summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.clang-format3
-rw-r--r--src/CMakeLists.txt27
-rw-r--r--src/audio_core/CMakeLists.txt259
-rw-r--r--src/audio_core/algorithm/filter.cpp80
-rw-r--r--src/audio_core/algorithm/filter.h62
-rw-r--r--src/audio_core/algorithm/interpolate.cpp233
-rw-r--r--src/audio_core/algorithm/interpolate.h44
-rw-r--r--src/audio_core/audio_core.cpp58
-rw-r--r--src/audio_core/audio_core.h90
-rw-r--r--src/audio_core/audio_event.cpp61
-rw-r--r--src/audio_core/audio_event.h92
-rw-r--r--src/audio_core/audio_in_manager.cpp91
-rw-r--r--src/audio_core/audio_in_manager.h93
-rw-r--r--src/audio_core/audio_manager.cpp81
-rw-r--r--src/audio_core/audio_manager.h86
-rw-r--r--src/audio_core/audio_out.cpp63
-rw-r--r--src/audio_core/audio_out.h50
-rw-r--r--src/audio_core/audio_out_manager.cpp81
-rw-r--r--src/audio_core/audio_out_manager.h89
-rw-r--r--src/audio_core/audio_render_manager.cpp70
-rw-r--r--src/audio_core/audio_render_manager.h103
-rw-r--r--src/audio_core/audio_renderer.cpp340
-rw-r--r--src/audio_core/audio_renderer.h79
-rw-r--r--src/audio_core/behavior_info.cpp105
-rw-r--r--src/audio_core/behavior_info.h72
-rw-r--r--src/audio_core/buffer.h45
-rw-r--r--src/audio_core/codec.cpp78
-rw-r--r--src/audio_core/codec.h44
-rw-r--r--src/audio_core/command_generator.cpp1370
-rw-r--r--src/audio_core/command_generator.h111
-rw-r--r--src/audio_core/common.h131
-rw-r--r--src/audio_core/common/audio_renderer_parameter.h60
-rw-r--r--src/audio_core/common/common.h138
-rw-r--r--src/audio_core/common/feature_support.h105
-rw-r--r--src/audio_core/common/wave_buffer.h35
-rw-r--r--src/audio_core/common/workbuffer_allocator.h100
-rw-r--r--src/audio_core/cubeb_sink.cpp271
-rw-r--r--src/audio_core/cubeb_sink.h36
-rw-r--r--src/audio_core/delay_line.cpp108
-rw-r--r--src/audio_core/delay_line.h50
-rw-r--r--src/audio_core/device/audio_buffer.h25
-rw-r--r--src/audio_core/device/audio_buffers.h317
-rw-r--r--src/audio_core/device/device_session.cpp132
-rw-r--r--src/audio_core/device/device_session.h148
-rw-r--r--src/audio_core/effect_context.cpp321
-rw-r--r--src/audio_core/effect_context.h350
-rw-r--r--src/audio_core/in/audio_in.cpp100
-rw-r--r--src/audio_core/in/audio_in.h147
-rw-r--r--src/audio_core/in/audio_in_system.cpp221
-rw-r--r--src/audio_core/in/audio_in_system.h275
-rw-r--r--src/audio_core/info_updater.cpp513
-rw-r--r--src/audio_core/info_updater.h58
-rw-r--r--src/audio_core/memory_pool.cpp61
-rw-r--r--src/audio_core/memory_pool.h52
-rw-r--r--src/audio_core/mix_context.cpp298
-rw-r--r--src/audio_core/mix_context.h114
-rw-r--r--src/audio_core/null_sink.h33
-rw-r--r--src/audio_core/out/audio_out.cpp100
-rw-r--r--src/audio_core/out/audio_out.h147
-rw-r--r--src/audio_core/out/audio_out_system.cpp216
-rw-r--r--src/audio_core/out/audio_out_system.h257
-rw-r--r--src/audio_core/renderer/adsp/adsp.cpp118
-rw-r--r--src/audio_core/renderer/adsp/adsp.h171
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.cpp219
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.h203
-rw-r--r--src/audio_core/renderer/adsp/command_buffer.h21
-rw-r--r--src/audio_core/renderer/adsp/command_list_processor.cpp109
-rw-r--r--src/audio_core/renderer/adsp/command_list_processor.h119
-rw-r--r--src/audio_core/renderer/audio_device.cpp74
-rw-r--r--src/audio_core/renderer/audio_device.h80
-rw-r--r--src/audio_core/renderer/audio_renderer.cpp67
-rw-r--r--src/audio_core/renderer/audio_renderer.h97
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.cpp193
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.h376
-rw-r--r--src/audio_core/renderer/behavior/info_updater.cpp539
-rw-r--r--src/audio_core/renderer/behavior/info_updater.h205
-rw-r--r--src/audio_core/renderer/command/command_buffer.cpp714
-rw-r--r--src/audio_core/renderer/command/command_buffer.h468
-rw-r--r--src/audio_core/renderer/command/command_generator.cpp796
-rw-r--r--src/audio_core/renderer/command/command_generator.h349
-rw-r--r--src/audio_core/renderer/command/command_list_header.h22
-rw-r--r--src/audio_core/renderer/command/command_processing_time_estimator.cpp3620
-rw-r--r--src/audio_core/renderer/command/command_processing_time_estimator.h254
-rw-r--r--src/audio_core/renderer/command/commands.h32
-rw-r--r--src/audio_core/renderer/command/data_source/adpcm.cpp84
-rw-r--r--src/audio_core/renderer/command/data_source/adpcm.h119
-rw-r--r--src/audio_core/renderer/command/data_source/decode.cpp428
-rw-r--r--src/audio_core/renderer/command/data_source/decode.h59
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_float.cpp86
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_float.h113
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_int16.cpp87
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_int16.h110
-rw-r--r--src/audio_core/renderer/command/effect/aux_.cpp207
-rw-r--r--src/audio_core/renderer/command/effect/aux_.h66
-rw-r--r--src/audio_core/renderer/command/effect/biquad_filter.cpp118
-rw-r--r--src/audio_core/renderer/command/effect/biquad_filter.h74
-rw-r--r--src/audio_core/renderer/command/effect/capture.cpp142
-rw-r--r--src/audio_core/renderer/command/effect/capture.h62
-rw-r--r--src/audio_core/renderer/command/effect/compressor.cpp155
-rw-r--r--src/audio_core/renderer/command/effect/compressor.h60
-rw-r--r--src/audio_core/renderer/command/effect/delay.cpp238
-rw-r--r--src/audio_core/renderer/command/effect/delay.h60
-rw-r--r--src/audio_core/renderer/command/effect/i3dl2_reverb.cpp437
-rw-r--r--src/audio_core/renderer/command/effect/i3dl2_reverb.h60
-rw-r--r--src/audio_core/renderer/command/effect/light_limiter.cpp222
-rw-r--r--src/audio_core/renderer/command/effect/light_limiter.h103
-rw-r--r--src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp45
-rw-r--r--src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h59
-rw-r--r--src/audio_core/renderer/command/effect/reverb.cpp440
-rw-r--r--src/audio_core/renderer/command/effect/reverb.h62
-rw-r--r--src/audio_core/renderer/command/icommand.h93
-rw-r--r--src/audio_core/renderer/command/mix/clear_mix.cpp24
-rw-r--r--src/audio_core/renderer/command/mix/clear_mix.h45
-rw-r--r--src/audio_core/renderer/command/mix/copy_mix.cpp27
-rw-r--r--src/audio_core/renderer/command/mix/copy_mix.h49
-rw-r--r--src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp64
-rw-r--r--src/audio_core/renderer/command/mix/depop_for_mix_buffers.h55
-rw-r--r--src/audio_core/renderer/command/mix/depop_prepare.cpp36
-rw-r--r--src/audio_core/renderer/command/mix/depop_prepare.h54
-rw-r--r--src/audio_core/renderer/command/mix/mix.cpp70
-rw-r--r--src/audio_core/renderer/command/mix/mix.h54
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.cpp82
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.h73
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp65
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp_grouped.h61
-rw-r--r--src/audio_core/renderer/command/mix/volume.cpp72
-rw-r--r--src/audio_core/renderer/command/mix/volume.h53
-rw-r--r--src/audio_core/renderer/command/mix/volume_ramp.cpp84
-rw-r--r--src/audio_core/renderer/command/mix/volume_ramp.h56
-rw-r--r--src/audio_core/renderer/command/performance/performance.cpp43
-rw-r--r--src/audio_core/renderer/command/performance/performance.h51
-rw-r--r--src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp74
-rw-r--r--src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h59
-rw-r--r--src/audio_core/renderer/command/resample/resample.cpp883
-rw-r--r--src/audio_core/renderer/command/resample/resample.h29
-rw-r--r--src/audio_core/renderer/command/resample/upsample.cpp262
-rw-r--r--src/audio_core/renderer/command/resample/upsample.h60
-rw-r--r--src/audio_core/renderer/command/sink/circular_buffer.cpp48
-rw-r--r--src/audio_core/renderer/command/sink/circular_buffer.h55
-rw-r--r--src/audio_core/renderer/command/sink/device.cpp59
-rw-r--r--src/audio_core/renderer/command/sink/device.h57
-rw-r--r--src/audio_core/renderer/effect/aux_.cpp93
-rw-r--r--src/audio_core/renderer/effect/aux_.h123
-rw-r--r--src/audio_core/renderer/effect/biquad_filter.cpp52
-rw-r--r--src/audio_core/renderer/effect/biquad_filter.h79
-rw-r--r--src/audio_core/renderer/effect/buffer_mixer.cpp49
-rw-r--r--src/audio_core/renderer/effect/buffer_mixer.h75
-rw-r--r--src/audio_core/renderer/effect/capture.cpp82
-rw-r--r--src/audio_core/renderer/effect/capture.h65
-rw-r--r--src/audio_core/renderer/effect/compressor.cpp40
-rw-r--r--src/audio_core/renderer/effect/compressor.h106
-rw-r--r--src/audio_core/renderer/effect/delay.cpp93
-rw-r--r--src/audio_core/renderer/effect/delay.h135
-rw-r--r--src/audio_core/renderer/effect/effect_context.cpp41
-rw-r--r--src/audio_core/renderer/effect/effect_context.h75
-rw-r--r--src/audio_core/renderer/effect/effect_info_base.h435
-rw-r--r--src/audio_core/renderer/effect/effect_reset.h71
-rw-r--r--src/audio_core/renderer/effect/effect_result_state.h16
-rw-r--r--src/audio_core/renderer/effect/i3dl2.cpp94
-rw-r--r--src/audio_core/renderer/effect/i3dl2.h200
-rw-r--r--src/audio_core/renderer/effect/light_limiter.cpp81
-rw-r--r--src/audio_core/renderer/effect/light_limiter.h138
-rw-r--r--src/audio_core/renderer/effect/reverb.cpp93
-rw-r--r--src/audio_core/renderer/effect/reverb.h190
-rw-r--r--src/audio_core/renderer/memory/address_info.h124
-rw-r--r--src/audio_core/renderer/memory/memory_pool_info.cpp61
-rw-r--r--src/audio_core/renderer/memory/memory_pool_info.h170
-rw-r--r--src/audio_core/renderer/memory/pool_mapper.cpp243
-rw-r--r--src/audio_core/renderer/memory/pool_mapper.h179
-rw-r--r--src/audio_core/renderer/mix/mix_context.cpp141
-rw-r--r--src/audio_core/renderer/mix/mix_context.h124
-rw-r--r--src/audio_core/renderer/mix/mix_info.cpp120
-rw-r--r--src/audio_core/renderer/mix/mix_info.h124
-rw-r--r--src/audio_core/renderer/nodes/bit_array.h25
-rw-r--r--src/audio_core/renderer/nodes/edge_matrix.cpp38
-rw-r--r--src/audio_core/renderer/nodes/edge_matrix.h82
-rw-r--r--src/audio_core/renderer/nodes/node_states.cpp141
-rw-r--r--src/audio_core/renderer/nodes/node_states.h195
-rw-r--r--src/audio_core/renderer/performance/detail_aspect.cpp25
-rw-r--r--src/audio_core/renderer/performance/detail_aspect.h33
-rw-r--r--src/audio_core/renderer/performance/entry_aspect.cpp23
-rw-r--r--src/audio_core/renderer/performance/entry_aspect.h32
-rw-r--r--src/audio_core/renderer/performance/performance_detail.h50
-rw-r--r--src/audio_core/renderer/performance/performance_entry.h37
-rw-r--r--src/audio_core/renderer/performance/performance_entry_addresses.h17
-rw-r--r--src/audio_core/renderer/performance/performance_frame_header.h36
-rw-r--r--src/audio_core/renderer/performance/performance_manager.cpp645
-rw-r--r--src/audio_core/renderer/performance/performance_manager.h275
-rw-r--r--src/audio_core/renderer/sink/circular_buffer_sink_info.cpp76
-rw-r--r--src/audio_core/renderer/sink/circular_buffer_sink_info.h41
-rw-r--r--src/audio_core/renderer/sink/device_sink_info.cpp57
-rw-r--r--src/audio_core/renderer/sink/device_sink_info.h40
-rw-r--r--src/audio_core/renderer/sink/sink_context.cpp21
-rw-r--r--src/audio_core/renderer/sink/sink_context.h47
-rw-r--r--src/audio_core/renderer/sink/sink_info_base.cpp51
-rw-r--r--src/audio_core/renderer/sink/sink_info_base.h177
-rw-r--r--src/audio_core/renderer/splitter/splitter_context.cpp217
-rw-r--r--src/audio_core/renderer/splitter/splitter_context.h189
-rw-r--r--src/audio_core/renderer/splitter/splitter_destinations_data.cpp87
-rw-r--r--src/audio_core/renderer/splitter/splitter_destinations_data.h135
-rw-r--r--src/audio_core/renderer/splitter/splitter_info.cpp79
-rw-r--r--src/audio_core/renderer/splitter/splitter_info.h107
-rw-r--r--src/audio_core/renderer/system.cpp802
-rw-r--r--src/audio_core/renderer/system.h307
-rw-r--r--src/audio_core/renderer/system_manager.cpp126
-rw-r--r--src/audio_core/renderer/system_manager.h104
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_info.cpp6
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_info.h35
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_manager.cpp44
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_manager.h45
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_state.h40
-rw-r--r--src/audio_core/renderer/voice/voice_channel_resource.h38
-rw-r--r--src/audio_core/renderer/voice/voice_context.cpp86
-rw-r--r--src/audio_core/renderer/voice/voice_context.h126
-rw-r--r--src/audio_core/renderer/voice/voice_info.cpp408
-rw-r--r--src/audio_core/renderer/voice/voice_info.h380
-rw-r--r--src/audio_core/renderer/voice/voice_state.h70
-rw-r--r--src/audio_core/sdl2_sink.cpp163
-rw-r--r--src/audio_core/sdl2_sink.h29
-rw-r--r--src/audio_core/sink.h31
-rw-r--r--src/audio_core/sink/cubeb_sink.cpp329
-rw-r--r--src/audio_core/sink/cubeb_sink.h99
-rw-r--r--src/audio_core/sink/null_sink.h57
-rw-r--r--src/audio_core/sink/sdl2_sink.cpp243
-rw-r--r--src/audio_core/sink/sdl2_sink.h90
-rw-r--r--src/audio_core/sink/sink.h95
-rw-r--r--src/audio_core/sink/sink_details.cpp91
-rw-r--r--src/audio_core/sink/sink_details.h43
-rw-r--r--src/audio_core/sink/sink_stream.cpp284
-rw-r--r--src/audio_core/sink/sink_stream.h249
-rw-r--r--src/audio_core/sink_context.cpp48
-rw-r--r--src/audio_core/sink_context.h96
-rw-r--r--src/audio_core/sink_details.cpp91
-rw-r--r--src/audio_core/sink_details.h24
-rw-r--r--src/audio_core/sink_stream.h36
-rw-r--r--src/audio_core/splitter_context.cpp617
-rw-r--r--src/audio_core/splitter_context.h219
-rw-r--r--src/audio_core/stream.cpp175
-rw-r--r--src/audio_core/stream.h131
-rw-r--r--src/audio_core/time_stretch.cpp68
-rw-r--r--src/audio_core/time_stretch.h34
-rw-r--r--src/audio_core/voice_context.cpp580
-rw-r--r--src/audio_core/voice_context.h303
-rw-r--r--src/common/CMakeLists.txt50
-rw-r--r--src/common/address_space.cpp10
-rw-r--r--src/common/address_space.h150
-rw-r--r--src/common/address_space.inc366
-rw-r--r--src/common/algorithm.h13
-rw-r--r--src/common/alignment.h3
-rw-r--r--src/common/announce_multiplayer_room.h140
-rw-r--r--src/common/assert.cpp12
-rw-r--r--src/common/assert.h61
-rw-r--r--src/common/atomic_helpers.h775
-rw-r--r--src/common/atomic_ops.h100
-rw-r--r--src/common/bit_cast.h5
-rw-r--r--src/common/bit_field.h42
-rw-r--r--src/common/bit_set.h17
-rw-r--r--src/common/bit_util.h12
-rw-r--r--src/common/bounded_threadsafe_queue.h167
-rw-r--r--src/common/cityhash.cpp25
-rw-r--r--src/common/cityhash.h25
-rw-r--r--src/common/common_funcs.h15
-rw-r--r--src/common/common_types.h4
-rw-r--r--src/common/concepts.h5
-rw-r--r--src/common/detached_tasks.cpp9
-rw-r--r--src/common/detached_tasks.h5
-rw-r--r--src/common/div_ceil.h5
-rw-r--r--src/common/dynamic_library.cpp6
-rw-r--r--src/common/dynamic_library.h5
-rw-r--r--src/common/elf.h333
-rw-r--r--src/common/error.cpp6
-rw-r--r--src/common/error.h6
-rw-r--r--src/common/expected.h5
-rw-r--r--src/common/fiber.cpp31
-rw-r--r--src/common/fiber.h12
-rw-r--r--src/common/fixed_point.h706
-rw-r--r--src/common/fs/file.cpp6
-rw-r--r--src/common/fs/file.h7
-rw-r--r--src/common/fs/fs.cpp5
-rw-r--r--src/common/fs/fs.h5
-rw-r--r--src/common/fs/fs_paths.h5
-rw-r--r--src/common/fs/fs_types.h6
-rw-r--r--src/common/fs/fs_util.cpp13
-rw-r--r--src/common/fs/fs_util.h24
-rw-r--r--src/common/fs/path_util.cpp9
-rw-r--r--src/common/fs/path_util.h5
-rw-r--r--src/common/hash.h12
-rw-r--r--src/common/hex_util.cpp6
-rw-r--r--src/common/hex_util.h7
-rw-r--r--src/common/host_memory.cpp15
-rw-r--r--src/common/host_memory.h5
-rw-r--r--src/common/input.h67
-rw-r--r--src/common/intrusive_red_black_tree.h395
-rw-r--r--src/common/literals.h5
-rw-r--r--src/common/logging/backend.cpp27
-rw-r--r--src/common/logging/backend.h6
-rw-r--r--src/common/logging/filter.cpp10
-rw-r--r--src/common/logging/filter.h6
-rw-r--r--src/common/logging/formatter.h5
-rw-r--r--src/common/logging/log.h5
-rw-r--r--src/common/logging/log_entry.h5
-rw-r--r--src/common/logging/text_formatter.cpp7
-rw-r--r--src/common/logging/text_formatter.h6
-rw-r--r--src/common/logging/types.h10
-rw-r--r--src/common/lru_cache.h5
-rw-r--r--src/common/lz4_compression.cpp5
-rw-r--r--src/common/lz4_compression.h5
-rw-r--r--src/common/math_util.h56
-rw-r--r--src/common/memory_detect.cpp7
-rw-r--r--src/common/memory_detect.h5
-rw-r--r--src/common/microprofile.cpp5
-rw-r--r--src/common/microprofile.h14
-rw-r--r--src/common/microprofileui.h5
-rw-r--r--src/common/multi_level_page_table.cpp9
-rw-r--r--src/common/multi_level_page_table.h78
-rw-r--r--src/common/multi_level_page_table.inc84
-rw-r--r--src/common/nvidia_flags.cpp6
-rw-r--r--src/common/nvidia_flags.h5
-rw-r--r--src/common/page_table.cpp63
-rw-r--r--src/common/page_table.h33
-rw-r--r--src/common/param_package.cpp11
-rw-r--r--src/common/param_package.h5
-rw-r--r--src/common/parent_of_member.h8
-rw-r--r--src/common/point.h5
-rw-r--r--src/common/quaternion.h5
-rw-r--r--src/common/reader_writer_queue.h940
-rw-r--r--src/common/ring_buffer.h6
-rw-r--r--src/common/scm_rev.cpp.in5
-rw-r--r--src/common/scm_rev.h5
-rw-r--r--src/common/scope_exit.h5
-rw-r--r--src/common/settings.cpp19
-rw-r--r--src/common/settings.h465
-rw-r--r--src/common/settings_input.cpp5
-rw-r--r--src/common/settings_input.h6
-rw-r--r--src/common/socket_types.h51
-rw-r--r--src/common/spin_lock.cpp5
-rw-r--r--src/common/spin_lock.h5
-rw-r--r--src/common/stream.cpp5
-rw-r--r--src/common/stream.h5
-rw-r--r--src/common/string_util.cpp12
-rw-r--r--src/common/string_util.h8
-rw-r--r--src/common/swap.h16
-rw-r--r--src/common/telemetry.cpp66
-rw-r--r--src/common/telemetry.h14
-rw-r--r--src/common/thread.cpp18
-rw-r--r--src/common/thread.h13
-rw-r--r--src/common/thread_queue_list.h6
-rw-r--r--src/common/thread_worker.h5
-rw-r--r--src/common/threadsafe_queue.h11
-rw-r--r--src/common/time_zone.cpp5
-rw-r--r--src/common/time_zone.h5
-rw-r--r--src/common/tiny_mt.h5
-rw-r--r--src/common/tree.h653
-rw-r--r--src/common/uint128.h8
-rw-r--r--src/common/unique_function.h5
-rw-r--r--src/common/uuid.cpp5
-rw-r--r--src/common/uuid.h6
-rw-r--r--src/common/vector_math.h32
-rw-r--r--src/common/virtual_buffer.cpp5
-rw-r--r--src/common/virtual_buffer.h6
-rw-r--r--src/common/wall_clock.cpp9
-rw-r--r--src/common/wall_clock.h5
-rw-r--r--src/common/x64/cpu_detect.cpp138
-rw-r--r--src/common/x64/cpu_detect.h85
-rw-r--r--src/common/x64/native_clock.cpp69
-rw-r--r--src/common/x64/native_clock.h13
-rw-r--r--src/common/x64/xbyak_abi.h5
-rw-r--r--src/common/x64/xbyak_util.h5
-rw-r--r--src/common/zstd_compression.cpp5
-rw-r--r--src/common/zstd_compression.h5
-rw-r--r--src/core/CMakeLists.txt151
-rw-r--r--src/core/arm/arm_interface.cpp332
-rw-r--r--src/core/arm/arm_interface.h69
-rw-r--r--src/core/arm/cpu_interrupt_handler.cpp25
-rw-r--r--src/core/arm/cpu_interrupt_handler.h40
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp311
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.h41
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp333
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.h41
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.cpp38
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.h7
-rw-r--r--src/core/arm/dynarmic/arm_exclusive_monitor.cpp9
-rw-r--r--src/core/arm/dynarmic/arm_exclusive_monitor.h9
-rw-r--r--src/core/arm/exclusive_monitor.cpp5
-rw-r--r--src/core/arm/exclusive_monitor.h7
-rw-r--r--src/core/arm/symbols.cpp130
-rw-r--r--src/core/arm/symbols.h26
-rw-r--r--src/core/constants.cpp5
-rw-r--r--src/core/constants.h5
-rw-r--r--src/core/core.cpp176
-rw-r--r--src/core/core.h79
-rw-r--r--src/core/core_timing.cpp104
-rw-r--r--src/core/core_timing.h28
-rw-r--r--src/core/core_timing_util.h5
-rw-r--r--src/core/cpu_manager.cpp320
-rw-r--r--src/core/cpu_manager.h55
-rw-r--r--src/core/crypto/aes_util.cpp5
-rw-r--r--src/core/crypto/aes_util.h5
-rw-r--r--src/core/crypto/ctr_encryption_layer.cpp5
-rw-r--r--src/core/crypto/ctr_encryption_layer.h5
-rw-r--r--src/core/crypto/encryption_layer.cpp5
-rw-r--r--src/core/crypto/encryption_layer.h5
-rw-r--r--src/core/crypto/key_manager.cpp7
-rw-r--r--src/core/crypto/key_manager.h5
-rw-r--r--src/core/crypto/partition_data_manager.cpp5
-rw-r--r--src/core/crypto/partition_data_manager.h5
-rw-r--r--src/core/crypto/sha_util.cpp5
-rw-r--r--src/core/crypto/sha_util.h5
-rw-r--r--src/core/crypto/xts_encryption_layer.cpp5
-rw-r--r--src/core/crypto/xts_encryption_layer.h5
-rw-r--r--src/core/debugger/debugger.cpp316
-rw-r--r--src/core/debugger/debugger.h52
-rw-r--r--src/core/debugger/debugger_interface.h90
-rw-r--r--src/core/debugger/gdbstub.cpp718
-rw-r--r--src/core/debugger/gdbstub.h52
-rw-r--r--src/core/debugger/gdbstub_arch.cpp487
-rw-r--r--src/core/debugger/gdbstub_arch.h68
-rw-r--r--src/core/device_memory.cpp10
-rw-r--r--src/core/device_memory.h9
-rw-r--r--src/core/file_sys/bis_factory.cpp5
-rw-r--r--src/core/file_sys/bis_factory.h5
-rw-r--r--src/core/file_sys/card_image.cpp5
-rw-r--r--src/core/file_sys/card_image.h5
-rw-r--r--src/core/file_sys/common_funcs.h5
-rw-r--r--src/core/file_sys/content_archive.cpp7
-rw-r--r--src/core/file_sys/content_archive.h5
-rw-r--r--src/core/file_sys/control_metadata.cpp5
-rw-r--r--src/core/file_sys/control_metadata.h5
-rw-r--r--src/core/file_sys/directory.h6
-rw-r--r--src/core/file_sys/errors.h23
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.cpp25
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.h25
-rw-r--r--src/core/file_sys/ips_layer.cpp12
-rw-r--r--src/core/file_sys/ips_layer.h5
-rw-r--r--src/core/file_sys/kernel_executable.cpp5
-rw-r--r--src/core/file_sys/kernel_executable.h5
-rw-r--r--src/core/file_sys/mode.h5
-rw-r--r--src/core/file_sys/nca_metadata.cpp5
-rw-r--r--src/core/file_sys/nca_metadata.h5
-rw-r--r--src/core/file_sys/nca_patch.cpp7
-rw-r--r--src/core/file_sys/nca_patch.h5
-rw-r--r--src/core/file_sys/partition_filesystem.cpp5
-rw-r--r--src/core/file_sys/partition_filesystem.h5
-rw-r--r--src/core/file_sys/patch_manager.cpp101
-rw-r--r--src/core/file_sys/patch_manager.h5
-rw-r--r--src/core/file_sys/program_metadata.cpp5
-rw-r--r--src/core/file_sys/program_metadata.h7
-rw-r--r--src/core/file_sys/registered_cache.cpp13
-rw-r--r--src/core/file_sys/registered_cache.h5
-rw-r--r--src/core/file_sys/romfs.cpp5
-rw-r--r--src/core/file_sys/romfs.h5
-rw-r--r--src/core/file_sys/romfs_factory.cpp5
-rw-r--r--src/core/file_sys/romfs_factory.h5
-rw-r--r--src/core/file_sys/savedata_factory.cpp5
-rw-r--r--src/core/file_sys/savedata_factory.h5
-rw-r--r--src/core/file_sys/sdmc_factory.cpp5
-rw-r--r--src/core/file_sys/sdmc_factory.h5
-rw-r--r--src/core/file_sys/submission_package.cpp5
-rw-r--r--src/core/file_sys/submission_package.h5
-rw-r--r--src/core/file_sys/system_archive/data/font_chinese_simplified.cpp5
-rw-r--r--src/core/file_sys/system_archive/data/font_chinese_simplified.h5
-rw-r--r--src/core/file_sys/system_archive/data/font_chinese_traditional.cpp5
-rw-r--r--src/core/file_sys/system_archive/data/font_chinese_traditional.h5
-rw-r--r--src/core/file_sys/system_archive/data/font_extended_chinese_simplified.cpp5
-rw-r--r--src/core/file_sys/system_archive/data/font_extended_chinese_simplified.h5
-rw-r--r--src/core/file_sys/system_archive/data/font_korean.cpp5
-rw-r--r--src/core/file_sys/system_archive/data/font_korean.h5
-rw-r--r--src/core/file_sys/system_archive/data/font_nintendo_extended.cpp5
-rw-r--r--src/core/file_sys/system_archive/data/font_nintendo_extended.h5
-rw-r--r--src/core/file_sys/system_archive/data/font_standard.cpp5
-rw-r--r--src/core/file_sys/system_archive/data/font_standard.h5
-rw-r--r--src/core/file_sys/system_archive/mii_model.cpp5
-rw-r--r--src/core/file_sys/system_archive/mii_model.h5
-rw-r--r--src/core/file_sys/system_archive/ng_word.cpp5
-rw-r--r--src/core/file_sys/system_archive/ng_word.h5
-rw-r--r--src/core/file_sys/system_archive/shared_font.cpp7
-rw-r--r--src/core/file_sys/system_archive/shared_font.h5
-rw-r--r--src/core/file_sys/system_archive/system_archive.cpp5
-rw-r--r--src/core/file_sys/system_archive/system_archive.h5
-rw-r--r--src/core/file_sys/system_archive/system_version.cpp5
-rw-r--r--src/core/file_sys/system_archive/system_version.h5
-rw-r--r--src/core/file_sys/system_archive/time_zone_binary.cpp5
-rw-r--r--src/core/file_sys/system_archive/time_zone_binary.h5
-rw-r--r--src/core/file_sys/vfs.cpp5
-rw-r--r--src/core/file_sys/vfs.h5
-rw-r--r--src/core/file_sys/vfs_concat.cpp5
-rw-r--r--src/core/file_sys/vfs_concat.h5
-rw-r--r--src/core/file_sys/vfs_layered.cpp5
-rw-r--r--src/core/file_sys/vfs_layered.h5
-rw-r--r--src/core/file_sys/vfs_offset.cpp5
-rw-r--r--src/core/file_sys/vfs_offset.h5
-rw-r--r--src/core/file_sys/vfs_real.cpp7
-rw-r--r--src/core/file_sys/vfs_real.h5
-rw-r--r--src/core/file_sys/vfs_static.h5
-rw-r--r--src/core/file_sys/vfs_types.h5
-rw-r--r--src/core/file_sys/vfs_vector.cpp5
-rw-r--r--src/core/file_sys/vfs_vector.h5
-rw-r--r--src/core/file_sys/xts_archive.cpp5
-rw-r--r--src/core/file_sys/xts_archive.h5
-rw-r--r--src/core/frontend/applets/controller.cpp7
-rw-r--r--src/core/frontend/applets/controller.h5
-rw-r--r--src/core/frontend/applets/error.cpp11
-rw-r--r--src/core/frontend/applets/error.h17
-rw-r--r--src/core/frontend/applets/general_frontend.cpp5
-rw-r--r--src/core/frontend/applets/general_frontend.h5
-rw-r--r--src/core/frontend/applets/mii_edit.cpp17
-rw-r--r--src/core/frontend/applets/mii_edit.h22
-rw-r--r--src/core/frontend/applets/profile_select.cpp5
-rw-r--r--src/core/frontend/applets/profile_select.h5
-rw-r--r--src/core/frontend/applets/software_keyboard.cpp5
-rw-r--r--src/core/frontend/applets/software_keyboard.h7
-rw-r--r--src/core/frontend/applets/web_browser.cpp5
-rw-r--r--src/core/frontend/applets/web_browser.h5
-rw-r--r--src/core/frontend/emu_window.cpp5
-rw-r--r--src/core/frontend/emu_window.h16
-rw-r--r--src/core/frontend/framebuffer_layout.cpp5
-rw-r--r--src/core/frontend/framebuffer_layout.h5
-rw-r--r--src/core/hardware_interrupt_manager.cpp30
-rw-r--r--src/core/hardware_interrupt_manager.h33
-rw-r--r--src/core/hardware_properties.h8
-rw-r--r--src/core/hid/emulated_console.cpp37
-rw-r--r--src/core/hid/emulated_console.h6
-rw-r--r--src/core/hid/emulated_controller.cpp625
-rw-r--r--src/core/hid/emulated_controller.h95
-rw-r--r--src/core/hid/emulated_devices.cpp83
-rw-r--r--src/core/hid/emulated_devices.h40
-rw-r--r--src/core/hid/hid_core.cpp9
-rw-r--r--src/core/hid/hid_core.h5
-rw-r--r--src/core/hid/hid_types.h134
-rw-r--r--src/core/hid/input_converter.cpp44
-rw-r--r--src/core/hid/input_converter.h21
-rw-r--r--src/core/hid/input_interpreter.cpp5
-rw-r--r--src/core/hid/input_interpreter.h5
-rw-r--r--src/core/hid/irs_types.h301
-rw-r--r--src/core/hid/motion_input.cpp5
-rw-r--r--src/core/hid/motion_input.h5
-rw-r--r--src/core/hle/api_version.h5
-rw-r--r--src/core/hle/ipc.h5
-rw-r--r--src/core/hle/ipc_helpers.h17
-rw-r--r--src/core/hle/kernel/arch/arm64/k_memory_region_device_types.inc5
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_memory_layout.h12
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_memory_region_device_types.inc5
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp47
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_system_control.h6
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/secure_monitor.h5
-rw-r--r--src/core/hle/kernel/code_set.cpp5
-rw-r--r--src/core/hle/kernel/code_set.h5
-rw-r--r--src/core/hle/kernel/global_scheduler_context.cpp12
-rw-r--r--src/core/hle/kernel/global_scheduler_context.h8
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp65
-rw-r--r--src/core/hle/kernel/hle_ipc.h45
-rw-r--r--src/core/hle/kernel/init/init_slab_setup.cpp106
-rw-r--r--src/core/hle/kernel/init/init_slab_setup.h10
-rw-r--r--src/core/hle/kernel/initial_process.h22
-rw-r--r--src/core/hle/kernel/k_address_arbiter.cpp32
-rw-r--r--src/core/hle/kernel/k_address_arbiter.h28
-rw-r--r--src/core/hle/kernel/k_address_space_info.cpp9
-rw-r--r--src/core/hle/kernel/k_address_space_info.h5
-rw-r--r--src/core/hle/kernel/k_affinity_mask.h8
-rw-r--r--src/core/hle/kernel/k_auto_object.cpp5
-rw-r--r--src/core/hle/kernel/k_auto_object.h23
-rw-r--r--src/core/hle/kernel/k_auto_object_container.cpp5
-rw-r--r--src/core/hle/kernel/k_auto_object_container.h5
-rw-r--r--src/core/hle/kernel/k_class_token.cpp5
-rw-r--r--src/core/hle/kernel/k_class_token.h8
-rw-r--r--src/core/hle/kernel/k_client_port.cpp9
-rw-r--r--src/core/hle/kernel/k_client_port.h9
-rw-r--r--src/core/hle/kernel/k_client_session.cpp9
-rw-r--r--src/core/hle/kernel/k_client_session.h11
-rw-r--r--src/core/hle/kernel/k_code_memory.cpp34
-rw-r--r--src/core/hle/kernel/k_code_memory.h25
-rw-r--r--src/core/hle/kernel/k_condition_variable.cpp26
-rw-r--r--src/core/hle/kernel/k_condition_variable.h11
-rw-r--r--src/core/hle/kernel/k_event.cpp19
-rw-r--r--src/core/hle/kernel/k_event.h7
-rw-r--r--src/core/hle/kernel/k_handle_table.cpp17
-rw-r--r--src/core/hle/kernel/k_handle_table.h45
-rw-r--r--src/core/hle/kernel/k_interrupt_manager.cpp17
-rw-r--r--src/core/hle/kernel/k_interrupt_manager.h5
-rw-r--r--src/core/hle/kernel/k_light_condition_variable.cpp8
-rw-r--r--src/core/hle/kernel/k_light_condition_variable.h5
-rw-r--r--src/core/hle/kernel/k_light_lock.cpp8
-rw-r--r--src/core/hle/kernel/k_light_lock.h5
-rw-r--r--src/core/hle/kernel/k_linked_list.h5
-rw-r--r--src/core/hle/kernel/k_memory_block.h5
-rw-r--r--src/core/hle/kernel/k_memory_block_manager.cpp5
-rw-r--r--src/core/hle/kernel/k_memory_block_manager.h5
-rw-r--r--src/core/hle/kernel/k_memory_layout.board.nintendo_nx.cpp5
-rw-r--r--src/core/hle/kernel/k_memory_layout.cpp5
-rw-r--r--src/core/hle/kernel/k_memory_layout.h15
-rw-r--r--src/core/hle/kernel/k_memory_manager.cpp475
-rw-r--r--src/core/hle/kernel/k_memory_manager.h174
-rw-r--r--src/core/hle/kernel/k_memory_region.h5
-rw-r--r--src/core/hle/kernel/k_memory_region_type.h15
-rw-r--r--src/core/hle/kernel/k_page_bitmap.h5
-rw-r--r--src/core/hle/kernel/k_page_buffer.cpp18
-rw-r--r--src/core/hle/kernel/k_page_buffer.h27
-rw-r--r--src/core/hle/kernel/k_page_group.h99
-rw-r--r--src/core/hle/kernel/k_page_heap.cpp131
-rw-r--r--src/core/hle/kernel/k_page_heap.h226
-rw-r--r--src/core/hle/kernel/k_page_linked_list.h96
-rw-r--r--src/core/hle/kernel/k_page_table.cpp1350
-rw-r--r--src/core/hle/kernel/k_page_table.h203
-rw-r--r--src/core/hle/kernel/k_port.cpp14
-rw-r--r--src/core/hle/kernel/k_port.h7
-rw-r--r--src/core/hle/kernel/k_priority_queue.h8
-rw-r--r--src/core/hle/kernel/k_process.cpp365
-rw-r--r--src/core/hle/kernel/k_process.h97
-rw-r--r--src/core/hle/kernel/k_readable_event.cpp11
-rw-r--r--src/core/hle/kernel/k_readable_event.h11
-rw-r--r--src/core/hle/kernel/k_resource_limit.cpp26
-rw-r--r--src/core/hle/kernel/k_resource_limit.h12
-rw-r--r--src/core/hle/kernel/k_scheduler.cpp744
-rw-r--r--src/core/hle/kernel/k_scheduler.h232
-rw-r--r--src/core/hle/kernel/k_scheduler_lock.h10
-rw-r--r--src/core/hle/kernel/k_scoped_lock.h8
-rw-r--r--src/core/hle/kernel/k_scoped_resource_reservation.h8
-rw-r--r--src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h8
-rw-r--r--src/core/hle/kernel/k_server_port.cpp11
-rw-r--r--src/core/hle/kernel/k_server_port.h13
-rw-r--r--src/core/hle/kernel/k_server_session.cpp37
-rw-r--r--src/core/hle/kernel/k_server_session.h17
-rw-r--r--src/core/hle/kernel/k_session.cpp5
-rw-r--r--src/core/hle/kernel/k_session.h5
-rw-r--r--src/core/hle/kernel/k_shared_memory.cpp21
-rw-r--r--src/core/hle/kernel/k_shared_memory.h23
-rw-r--r--src/core/hle/kernel/k_shared_memory_info.h5
-rw-r--r--src/core/hle/kernel/k_slab_heap.h235
-rw-r--r--src/core/hle/kernel/k_spin_lock.cpp44
-rw-r--r--src/core/hle/kernel/k_spin_lock.h9
-rw-r--r--src/core/hle/kernel/k_synchronization_object.cpp18
-rw-r--r--src/core/hle/kernel/k_synchronization_object.h13
-rw-r--r--src/core/hle/kernel/k_system_control.h5
-rw-r--r--src/core/hle/kernel/k_thread.cpp193
-rw-r--r--src/core/hle/kernel/k_thread.h141
-rw-r--r--src/core/hle/kernel/k_thread_local_page.cpp67
-rw-r--r--src/core/hle/kernel/k_thread_local_page.h110
-rw-r--r--src/core/hle/kernel/k_thread_queue.cpp14
-rw-r--r--src/core/hle/kernel/k_thread_queue.h14
-rw-r--r--src/core/hle/kernel/k_trace.h5
-rw-r--r--src/core/hle/kernel/k_transfer_memory.cpp9
-rw-r--r--src/core/hle/kernel/k_transfer_memory.h9
-rw-r--r--src/core/hle/kernel/k_worker_task.h5
-rw-r--r--src/core/hle/kernel/k_worker_task_manager.cpp7
-rw-r--r--src/core/hle/kernel/k_worker_task_manager.h5
-rw-r--r--src/core/hle/kernel/k_writable_event.cpp9
-rw-r--r--src/core/hle/kernel/k_writable_event.h9
-rw-r--r--src/core/hle/kernel/kernel.cpp442
-rw-r--r--src/core/hle/kernel/kernel.h79
-rw-r--r--src/core/hle/kernel/memory_types.h5
-rw-r--r--src/core/hle/kernel/physical_core.cpp37
-rw-r--r--src/core/hle/kernel/physical_core.h27
-rw-r--r--src/core/hle/kernel/physical_memory.h5
-rw-r--r--src/core/hle/kernel/process_capability.cpp48
-rw-r--r--src/core/hle/kernel/process_capability.h43
-rw-r--r--src/core/hle/kernel/service_thread.cpp12
-rw-r--r--src/core/hle/kernel/service_thread.h5
-rw-r--r--src/core/hle/kernel/slab_helpers.h7
-rw-r--r--src/core/hle/kernel/svc.cpp540
-rw-r--r--src/core/hle/kernel/svc.h5
-rw-r--r--src/core/hle/kernel/svc_common.h5
-rw-r--r--src/core/hle/kernel/svc_results.h63
-rw-r--r--src/core/hle/kernel/svc_types.h7
-rw-r--r--src/core/hle/kernel/svc_wrap.h147
-rw-r--r--src/core/hle/kernel/time_manager.cpp29
-rw-r--r--src/core/hle/kernel/time_manager.h5
-rw-r--r--src/core/hle/result.h81
-rw-r--r--src/core/hle/service/acc/acc.cpp42
-rw-r--r--src/core/hle/service/acc/acc.h7
-rw-r--r--src/core/hle/service/acc/acc_aa.cpp5
-rw-r--r--src/core/hle/service/acc/acc_aa.h5
-rw-r--r--src/core/hle/service/acc/acc_su.cpp5
-rw-r--r--src/core/hle/service/acc/acc_su.h5
-rw-r--r--src/core/hle/service/acc/acc_u0.cpp5
-rw-r--r--src/core/hle/service/acc/acc_u0.h5
-rw-r--r--src/core/hle/service/acc/acc_u1.cpp5
-rw-r--r--src/core/hle/service/acc/acc_u1.h5
-rw-r--r--src/core/hle/service/acc/async_context.cpp5
-rw-r--r--src/core/hle/service/acc/async_context.h7
-rw-r--r--src/core/hle/service/acc/errors.h9
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp28
-rw-r--r--src/core/hle/service/acc/profile_manager.h27
-rw-r--r--src/core/hle/service/am/am.cpp63
-rw-r--r--src/core/hle/service/am/am.h57
-rw-r--r--src/core/hle/service/am/applet_ae.cpp5
-rw-r--r--src/core/hle/service/am/applet_ae.h5
-rw-r--r--src/core/hle/service/am/applet_oe.cpp5
-rw-r--r--src/core/hle/service/am/applet_oe.h5
-rw-r--r--src/core/hle/service/am/applets/applet_controller.cpp13
-rw-r--r--src/core/hle/service/am/applets/applet_controller.h9
-rw-r--r--src/core/hle/service/am/applets/applet_error.cpp25
-rw-r--r--src/core/hle/service/am/applets/applet_error.h9
-rw-r--r--src/core/hle/service/am/applets/applet_general_backend.cpp19
-rw-r--r--src/core/hle/service/am/applets/applet_general_backend.h11
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.cpp138
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.h44
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit_types.h82
-rw-r--r--src/core/hle/service/am/applets/applet_profile_select.cpp11
-rw-r--r--src/core/hle/service/am/applets/applet_profile_select.h9
-rw-r--r--src/core/hle/service/am/applets/applet_software_keyboard.cpp489
-rw-r--r--src/core/hle/service/am/applets/applet_software_keyboard.h44
-rw-r--r--src/core/hle/service/am/applets/applet_software_keyboard_types.h77
-rw-r--r--src/core/hle/service/am/applets/applet_web_browser.cpp21
-rw-r--r--src/core/hle/service/am/applets/applet_web_browser.h9
-rw-r--r--src/core/hle/service/am/applets/applet_web_browser_types.h5
-rw-r--r--src/core/hle/service/am/applets/applets.cpp20
-rw-r--r--src/core/hle/service/am/applets/applets.h18
-rw-r--r--src/core/hle/service/am/idle.cpp5
-rw-r--r--src/core/hle/service/am/idle.h5
-rw-r--r--src/core/hle/service/am/omm.cpp5
-rw-r--r--src/core/hle/service/am/omm.h5
-rw-r--r--src/core/hle/service/am/spsm.cpp5
-rw-r--r--src/core/hle/service/am/spsm.h5
-rw-r--r--src/core/hle/service/am/tcap.cpp5
-rw-r--r--src/core/hle/service/am/tcap.h5
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp5
-rw-r--r--src/core/hle/service/aoc/aoc_u.h5
-rw-r--r--src/core/hle/service/apm/apm.cpp5
-rw-r--r--src/core/hle/service/apm/apm.h5
-rw-r--r--src/core/hle/service/apm/apm_controller.cpp17
-rw-r--r--src/core/hle/service/apm/apm_controller.h20
-rw-r--r--src/core/hle/service/apm/apm_interface.cpp5
-rw-r--r--src/core/hle/service/apm/apm_interface.h5
-rw-r--r--src/core/hle/service/audio/audctl.cpp5
-rw-r--r--src/core/hle/service/audio/audctl.h5
-rw-r--r--src/core/hle/service/audio/auddbg.cpp5
-rw-r--r--src/core/hle/service/audio/auddbg.h5
-rw-r--r--src/core/hle/service/audio/audin_a.cpp5
-rw-r--r--src/core/hle/service/audio/audin_a.h5
-rw-r--r--src/core/hle/service/audio/audin_u.cpp389
-rw-r--r--src/core/hle/service/audio/audin_u.h52
-rw-r--r--src/core/hle/service/audio/audio.cpp5
-rw-r--r--src/core/hle/service/audio/audio.h5
-rw-r--r--src/core/hle/service/audio/audout_a.cpp5
-rw-r--r--src/core/hle/service/audio/audout_a.h5
-rw-r--r--src/core/hle/service/audio/audout_u.cpp332
-rw-r--r--src/core/hle/service/audio/audout_u.h26
-rw-r--r--src/core/hle/service/audio/audrec_a.cpp5
-rw-r--r--src/core/hle/service/audio/audrec_a.h5
-rw-r--r--src/core/hle/service/audio/audrec_u.cpp5
-rw-r--r--src/core/hle/service/audio/audrec_u.h5
-rw-r--r--src/core/hle/service/audio/audren_a.cpp5
-rw-r--r--src/core/hle/service/audio/audren_a.h5
-rw-r--r--src/core/hle/service/audio/audren_u.cpp713
-rw-r--r--src/core/hle/service/audio/audren_u.h27
-rw-r--r--src/core/hle/service/audio/codecctl.cpp5
-rw-r--r--src/core/hle/service/audio/codecctl.h5
-rw-r--r--src/core/hle/service/audio/errors.h20
-rw-r--r--src/core/hle/service/audio/hwopus.cpp35
-rw-r--r--src/core/hle/service/audio/hwopus.h16
-rw-r--r--src/core/hle/service/bcat/backend/backend.cpp7
-rw-r--r--src/core/hle/service/bcat/backend/backend.h9
-rw-r--r--src/core/hle/service/bcat/bcat.cpp5
-rw-r--r--src/core/hle/service/bcat/bcat.h5
-rw-r--r--src/core/hle/service/bcat/bcat_module.cpp19
-rw-r--r--src/core/hle/service/bcat/bcat_module.h5
-rw-r--r--src/core/hle/service/bpc/bpc.cpp5
-rw-r--r--src/core/hle/service/bpc/bpc.h5
-rw-r--r--src/core/hle/service/btdrv/btdrv.cpp10
-rw-r--r--src/core/hle/service/btdrv/btdrv.h5
-rw-r--r--src/core/hle/service/btm/btm.cpp13
-rw-r--r--src/core/hle/service/btm/btm.h5
-rw-r--r--src/core/hle/service/caps/caps.cpp5
-rw-r--r--src/core/hle/service/caps/caps.h5
-rw-r--r--src/core/hle/service/caps/caps_a.cpp5
-rw-r--r--src/core/hle/service/caps/caps_a.h5
-rw-r--r--src/core/hle/service/caps/caps_c.cpp5
-rw-r--r--src/core/hle/service/caps/caps_c.h5
-rw-r--r--src/core/hle/service/caps/caps_sc.cpp5
-rw-r--r--src/core/hle/service/caps/caps_sc.h5
-rw-r--r--src/core/hle/service/caps/caps_ss.cpp5
-rw-r--r--src/core/hle/service/caps/caps_ss.h5
-rw-r--r--src/core/hle/service/caps/caps_su.cpp5
-rw-r--r--src/core/hle/service/caps/caps_su.h5
-rw-r--r--src/core/hle/service/caps/caps_u.cpp5
-rw-r--r--src/core/hle/service/caps/caps_u.h5
-rw-r--r--src/core/hle/service/erpt/erpt.cpp5
-rw-r--r--src/core/hle/service/erpt/erpt.h5
-rw-r--r--src/core/hle/service/es/es.cpp9
-rw-r--r--src/core/hle/service/es/es.h5
-rw-r--r--src/core/hle/service/eupld/eupld.cpp5
-rw-r--r--src/core/hle/service/eupld/eupld.h5
-rw-r--r--src/core/hle/service/fatal/fatal.cpp16
-rw-r--r--src/core/hle/service/fatal/fatal.h5
-rw-r--r--src/core/hle/service/fatal/fatal_p.cpp13
-rw-r--r--src/core/hle/service/fatal/fatal_p.h5
-rw-r--r--src/core/hle/service/fatal/fatal_u.cpp5
-rw-r--r--src/core/hle/service/fatal/fatal_u.h5
-rw-r--r--src/core/hle/service/fgm/fgm.cpp5
-rw-r--r--src/core/hle/service/fgm/fgm.h5
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp36
-rw-r--r--src/core/hle/service/filesystem/filesystem.h31
-rw-r--r--src/core/hle/service/filesystem/fsp_ldr.cpp5
-rw-r--r--src/core/hle/service/filesystem/fsp_ldr.h5
-rw-r--r--src/core/hle/service/filesystem/fsp_pr.cpp5
-rw-r--r--src/core/hle/service/filesystem/fsp_pr.h5
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp23
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h5
-rw-r--r--src/core/hle/service/friend/errors.h7
-rw-r--r--src/core/hle/service/friend/friend.cpp5
-rw-r--r--src/core/hle/service/friend/friend.h5
-rw-r--r--src/core/hle/service/friend/friend_interface.cpp5
-rw-r--r--src/core/hle/service/friend/friend_interface.h5
-rw-r--r--src/core/hle/service/glue/arp.cpp7
-rw-r--r--src/core/hle/service/glue/arp.h5
-rw-r--r--src/core/hle/service/glue/bgtc.cpp5
-rw-r--r--src/core/hle/service/glue/bgtc.h5
-rw-r--r--src/core/hle/service/glue/ectx.cpp5
-rw-r--r--src/core/hle/service/glue/ectx.h5
-rw-r--r--src/core/hle/service/glue/errors.h13
-rw-r--r--src/core/hle/service/glue/glue.cpp5
-rw-r--r--src/core/hle/service/glue/glue.h5
-rw-r--r--src/core/hle/service/glue/glue_manager.cpp11
-rw-r--r--src/core/hle/service/glue/glue_manager.h9
-rw-r--r--src/core/hle/service/glue/notif.cpp137
-rw-r--r--src/core/hle/service/glue/notif.h53
-rw-r--r--src/core/hle/service/grc/grc.cpp5
-rw-r--r--src/core/hle/service/grc/grc.h5
-rw-r--r--src/core/hle/service/hid/controllers/console_sixaxis.cpp26
-rw-r--r--src/core/hle/service/hid/controllers/console_sixaxis.h17
-rw-r--r--src/core/hle/service/hid/controllers/controller_base.cpp5
-rw-r--r--src/core/hle/service/hid/controllers/controller_base.h13
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.cpp24
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.h37
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp41
-rw-r--r--src/core/hle/service/hid/controllers/gesture.h53
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.cpp24
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.h36
-rw-r--r--src/core/hle/service/hid/controllers/mouse.cpp25
-rw-r--r--src/core/hle/service/hid/controllers/mouse.h29
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp1075
-rw-r--r--src/core/hle/service/hid/controllers/npad.h246
-rw-r--r--src/core/hle/service/hid/controllers/palma.cpp229
-rw-r--r--src/core/hle/service/hid/controllers/palma.h163
-rw-r--r--src/core/hle/service/hid/controllers/stubbed.cpp16
-rw-r--r--src/core/hle/service/hid/controllers/stubbed.h18
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp34
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h36
-rw-r--r--src/core/hle/service/hid/controllers/xpad.cpp26
-rw-r--r--src/core/hle/service/hid/controllers/xpad.h33
-rw-r--r--src/core/hle/service/hid/errors.h25
-rw-r--r--src/core/hle/service/hid/hid.cpp983
-rw-r--r--src/core/hle/service/hid/hid.h50
-rw-r--r--src/core/hle/service/hid/hidbus.cpp524
-rw-r--r--src/core/hle/service/hid/hidbus.h130
-rw-r--r--src/core/hle/service/hid/hidbus/hidbus_base.cpp71
-rw-r--r--src/core/hle/service/hid/hidbus/hidbus_base.h178
-rw-r--r--src/core/hle/service/hid/hidbus/ringcon.cpp285
-rw-r--r--src/core/hle/service/hid/hidbus/ringcon.h253
-rw-r--r--src/core/hle/service/hid/hidbus/starlink.cpp50
-rw-r--r--src/core/hle/service/hid/hidbus/starlink.h38
-rw-r--r--src/core/hle/service/hid/hidbus/stubbed.cpp51
-rw-r--r--src/core/hle/service/hid/hidbus/stubbed.h38
-rw-r--r--src/core/hle/service/hid/irs.cpp457
-rw-r--r--src/core/hle/service/hid/irs.h80
-rw-r--r--src/core/hle/service/hid/irs_ring_lifo.h47
-rw-r--r--src/core/hle/service/hid/irsensor/clustering_processor.cpp265
-rw-r--r--src/core/hle/service/hid/irsensor/clustering_processor.h110
-rw-r--r--src/core/hle/service/hid/irsensor/image_transfer_processor.cpp150
-rw-r--r--src/core/hle/service/hid/irsensor/image_transfer_processor.h73
-rw-r--r--src/core/hle/service/hid/irsensor/ir_led_processor.cpp27
-rw-r--r--src/core/hle/service/hid/irsensor/ir_led_processor.h47
-rw-r--r--src/core/hle/service/hid/irsensor/moment_processor.cpp34
-rw-r--r--src/core/hle/service/hid/irsensor/moment_processor.h61
-rw-r--r--src/core/hle/service/hid/irsensor/pointing_processor.cpp26
-rw-r--r--src/core/hle/service/hid/irsensor/pointing_processor.h61
-rw-r--r--src/core/hle/service/hid/irsensor/processor_base.cpp67
-rw-r--r--src/core/hle/service/hid/irsensor/processor_base.h33
-rw-r--r--src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp29
-rw-r--r--src/core/hle/service/hid/irsensor/tera_plugin_processor.h53
-rw-r--r--src/core/hle/service/hid/ring_lifo.h5
-rw-r--r--src/core/hle/service/hid/xcd.cpp5
-rw-r--r--src/core/hle/service/hid/xcd.h5
-rw-r--r--src/core/hle/service/jit/jit.cpp404
-rw-r--r--src/core/hle/service/jit/jit.h19
-rw-r--r--src/core/hle/service/jit/jit_context.cpp426
-rw-r--r--src/core/hle/service/jit/jit_context.h65
-rw-r--r--src/core/hle/service/kernel_helpers.cpp13
-rw-r--r--src/core/hle/service/kernel_helpers.h5
-rw-r--r--src/core/hle/service/lbl/lbl.cpp5
-rw-r--r--src/core/hle/service/lbl/lbl.h5
-rw-r--r--src/core/hle/service/ldn/errors.h13
-rw-r--r--src/core/hle/service/ldn/lan_discovery.cpp633
-rw-r--r--src/core/hle/service/ldn/lan_discovery.h134
-rw-r--r--src/core/hle/service/ldn/ldn.cpp478
-rw-r--r--src/core/hle/service/ldn/ldn.h11
-rw-r--r--src/core/hle/service/ldn/ldn_results.h27
-rw-r--r--src/core/hle/service/ldn/ldn_types.h306
-rw-r--r--src/core/hle/service/ldr/ldr.cpp154
-rw-r--r--src/core/hle/service/ldr/ldr.h5
-rw-r--r--src/core/hle/service/lm/lm.cpp5
-rw-r--r--src/core/hle/service/lm/lm.h5
-rw-r--r--src/core/hle/service/mig/mig.cpp5
-rw-r--r--src/core/hle/service/mig/mig.h5
-rw-r--r--src/core/hle/service/mii/mii.cpp39
-rw-r--r--src/core/hle/service/mii/mii.h5
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp250
-rw-r--r--src/core/hle/service/mii/mii_manager.h317
-rw-r--r--src/core/hle/service/mii/raw_data.cpp21
-rw-r--r--src/core/hle/service/mii/raw_data.h7
-rw-r--r--src/core/hle/service/mii/types.h436
-rw-r--r--src/core/hle/service/mm/mm_u.cpp15
-rw-r--r--src/core/hle/service/mm/mm_u.h5
-rw-r--r--src/core/hle/service/mnpp/mnpp_app.cpp44
-rw-r--r--src/core/hle/service/mnpp/mnpp_app.h19
-rw-r--r--src/core/hle/service/ncm/ncm.cpp5
-rw-r--r--src/core/hle/service/ncm/ncm.h5
-rw-r--r--src/core/hle/service/nfc/nfc.cpp13
-rw-r--r--src/core/hle/service/nfc/nfc.h5
-rw-r--r--src/core/hle/service/nfp/amiibo_crypto.cpp388
-rw-r--r--src/core/hle/service/nfp/amiibo_crypto.h100
-rw-r--r--src/core/hle/service/nfp/nfp.cpp348
-rw-r--r--src/core/hle/service/nfp/nfp.h49
-rw-r--r--src/core/hle/service/nfp/nfp_device.cpp681
-rw-r--r--src/core/hle/service/nfp/nfp_device.h101
-rw-r--r--src/core/hle/service/nfp/nfp_result.h24
-rw-r--r--src/core/hle/service/nfp/nfp_types.h320
-rw-r--r--src/core/hle/service/nfp/nfp_user.cpp669
-rw-r--r--src/core/hle/service/nfp/nfp_user.h49
-rw-r--r--src/core/hle/service/ngct/ngct.cpp5
-rw-r--r--src/core/hle/service/ngct/ngct.h5
-rw-r--r--src/core/hle/service/nifm/nifm.cpp360
-rw-r--r--src/core/hle/service/nifm/nifm.h32
-rw-r--r--src/core/hle/service/nim/nim.cpp5
-rw-r--r--src/core/hle/service/nim/nim.h5
-rw-r--r--src/core/hle/service/npns/npns.cpp5
-rw-r--r--src/core/hle/service/npns/npns.h5
-rw-r--r--src/core/hle/service/ns/errors.h7
-rw-r--r--src/core/hle/service/ns/iplatform_service_manager.cpp299
-rw-r--r--src/core/hle/service/ns/iplatform_service_manager.h58
-rw-r--r--src/core/hle/service/ns/language.cpp5
-rw-r--r--src/core/hle/service/ns/language.h5
-rw-r--r--src/core/hle/service/ns/ns.cpp10
-rw-r--r--src/core/hle/service/ns/ns.h5
-rw-r--r--src/core/hle/service/ns/pdm_qry.cpp6
-rw-r--r--src/core/hle/service/ns/pdm_qry.h5
-rw-r--r--src/core/hle/service/ns/pl_u.cpp300
-rw-r--r--src/core/hle/service/ns/pl_u.h59
-rw-r--r--src/core/hle/service/nvdrv/core/container.cpp50
-rw-r--r--src/core/hle/service/nvdrv/core/container.h52
-rw-r--r--src/core/hle/service/nvdrv/core/nvmap.cpp272
-rw-r--r--src/core/hle/service/nvdrv/core/nvmap.h175
-rw-r--r--src/core/hle/service/nvdrv/core/syncpoint_manager.cpp121
-rw-r--r--src/core/hle/service/nvdrv/core/syncpoint_manager.h134
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdevice.h13
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp34
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.h27
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp493
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h192
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp364
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.h115
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp30
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h21
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp135
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h66
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp28
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.h11
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp86
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h28
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.cpp25
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.h11
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp235
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.h61
-rw-r--r--src/core/hle/service/nvdrv/nvdata.h28
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.cpp131
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.h127
-rw-r--r--src/core/hle/service/nvdrv/nvdrv_interface.cpp46
-rw-r--r--src/core/hle/service/nvdrv/nvdrv_interface.h7
-rw-r--r--src/core/hle/service/nvdrv/nvmemp.cpp5
-rw-r--r--src/core/hle/service/nvdrv/nvmemp.h5
-rw-r--r--src/core/hle/service/nvdrv/syncpoint_manager.cpp39
-rw-r--r--src/core/hle/service/nvdrv/syncpoint_manager.h85
-rw-r--r--src/core/hle/service/nvflinger/binder.h43
-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.cpp213
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_consumer.h43
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_core.cpp113
-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.cpp927
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_producer.h90
-rw-r--r--src/core/hle/service/nvflinger/buffer_slot.h38
-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.cpp182
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h38
-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/olsc/olsc.cpp5
-rw-r--r--src/core/hle/service/olsc/olsc.h5
-rw-r--r--src/core/hle/service/pcie/pcie.cpp5
-rw-r--r--src/core/hle/service/pcie/pcie.h5
-rw-r--r--src/core/hle/service/pctl/pctl.cpp5
-rw-r--r--src/core/hle/service/pctl/pctl.h5
-rw-r--r--src/core/hle/service/pctl/pctl_module.cpp13
-rw-r--r--src/core/hle/service/pctl/pctl_module.h5
-rw-r--r--src/core/hle/service/pcv/pcv.cpp98
-rw-r--r--src/core/hle/service/pcv/pcv.h96
-rw-r--r--src/core/hle/service/pm/pm.cpp17
-rw-r--r--src/core/hle/service/pm/pm.h5
-rw-r--r--src/core/hle/service/prepo/prepo.cpp5
-rw-r--r--src/core/hle/service/prepo/prepo.h5
-rw-r--r--src/core/hle/service/psc/psc.cpp5
-rw-r--r--src/core/hle/service/psc/psc.h5
-rw-r--r--src/core/hle/service/ptm/psm.cpp127
-rw-r--r--src/core/hle/service/ptm/psm.h36
-rw-r--r--src/core/hle/service/ptm/ptm.cpp18
-rw-r--r--src/core/hle/service/ptm/ptm.h18
-rw-r--r--src/core/hle/service/ptm/ts.cpp41
-rw-r--r--src/core/hle/service/ptm/ts.h25
-rw-r--r--src/core/hle/service/service.cpp35
-rw-r--r--src/core/hle/service/service.h32
-rw-r--r--src/core/hle/service/set/set.cpp7
-rw-r--r--src/core/hle/service/set/set.h5
-rw-r--r--src/core/hle/service/set/set_cal.cpp5
-rw-r--r--src/core/hle/service/set/set_cal.h5
-rw-r--r--src/core/hle/service/set/set_fd.cpp5
-rw-r--r--src/core/hle/service/set/set_fd.h5
-rw-r--r--src/core/hle/service/set/set_sys.cpp7
-rw-r--r--src/core/hle/service/set/set_sys.h5
-rw-r--r--src/core/hle/service/set/settings.cpp5
-rw-r--r--src/core/hle/service/set/settings.h5
-rw-r--r--src/core/hle/service/sm/sm.cpp35
-rw-r--r--src/core/hle/service/sm/sm.h13
-rw-r--r--src/core/hle/service/sm/sm_controller.cpp7
-rw-r--r--src/core/hle/service/sm/sm_controller.h5
-rw-r--r--src/core/hle/service/sockets/bsd.cpp84
-rw-r--r--src/core/hle/service/sockets/bsd.h20
-rw-r--r--src/core/hle/service/sockets/ethc.cpp5
-rw-r--r--src/core/hle/service/sockets/ethc.h5
-rw-r--r--src/core/hle/service/sockets/nsd.cpp5
-rw-r--r--src/core/hle/service/sockets/nsd.h5
-rw-r--r--src/core/hle/service/sockets/sfdnsres.cpp210
-rw-r--r--src/core/hle/service/sockets/sfdnsres.h6
-rw-r--r--src/core/hle/service/sockets/sockets.cpp5
-rw-r--r--src/core/hle/service/sockets/sockets.h12
-rw-r--r--src/core/hle/service/sockets/sockets_translate.cpp9
-rw-r--r--src/core/hle/service/sockets/sockets_translate.h7
-rw-r--r--src/core/hle/service/spl/csrng.cpp5
-rw-r--r--src/core/hle/service/spl/csrng.h5
-rw-r--r--src/core/hle/service/spl/spl.cpp5
-rw-r--r--src/core/hle/service/spl/spl.h5
-rw-r--r--src/core/hle/service/spl/spl_module.cpp5
-rw-r--r--src/core/hle/service/spl/spl_module.h5
-rw-r--r--src/core/hle/service/spl/spl_results.h37
-rw-r--r--src/core/hle/service/spl/spl_types.h5
-rw-r--r--src/core/hle/service/ssl/ssl.cpp5
-rw-r--r--src/core/hle/service/ssl/ssl.h5
-rw-r--r--src/core/hle/service/time/clock_types.h13
-rw-r--r--src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h5
-rw-r--r--src/core/hle/service/time/ephemeral_network_system_clock_core.h5
-rw-r--r--src/core/hle/service/time/errors.h25
-rw-r--r--src/core/hle/service/time/local_system_clock_context_writer.h7
-rw-r--r--src/core/hle/service/time/network_system_clock_context_writer.h7
-rw-r--r--src/core/hle/service/time/standard_local_system_clock_core.h5
-rw-r--r--src/core/hle/service/time/standard_network_system_clock_core.h5
-rw-r--r--src/core/hle/service/time/standard_steady_clock_core.cpp5
-rw-r--r--src/core/hle/service/time/standard_steady_clock_core.h5
-rw-r--r--src/core/hle/service/time/standard_user_system_clock_core.cpp31
-rw-r--r--src/core/hle/service/time/standard_user_system_clock_core.h15
-rw-r--r--src/core/hle/service/time/steady_clock_core.h5
-rw-r--r--src/core/hle/service/time/system_clock_context_update_callback.cpp11
-rw-r--r--src/core/hle/service/time/system_clock_context_update_callback.h9
-rw-r--r--src/core/hle/service/time/system_clock_core.cpp19
-rw-r--r--src/core/hle/service/time/system_clock_core.h19
-rw-r--r--src/core/hle/service/time/tick_based_steady_clock_core.cpp5
-rw-r--r--src/core/hle/service/time/tick_based_steady_clock_core.h5
-rw-r--r--src/core/hle/service/time/time.cpp30
-rw-r--r--src/core/hle/service/time/time.h7
-rw-r--r--src/core/hle/service/time/time_interface.cpp5
-rw-r--r--src/core/hle/service/time/time_interface.h5
-rw-r--r--src/core/hle/service/time/time_manager.cpp15
-rw-r--r--src/core/hle/service/time/time_manager.h5
-rw-r--r--src/core/hle/service/time/time_sharedmemory.cpp5
-rw-r--r--src/core/hle/service/time/time_sharedmemory.h5
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.cpp15
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.h11
-rw-r--r--src/core/hle/service/time/time_zone_manager.cpp60
-rw-r--r--src/core/hle/service/time/time_zone_manager.h25
-rw-r--r--src/core/hle/service/time/time_zone_service.cpp20
-rw-r--r--src/core/hle/service/time/time_zone_service.h5
-rw-r--r--src/core/hle/service/time/time_zone_types.h5
-rw-r--r--src/core/hle/service/usb/usb.cpp5
-rw-r--r--src/core/hle/service/usb/usb.h5
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp74
-rw-r--r--src/core/hle/service/vi/display/vi_display.h53
-rw-r--r--src/core/hle/service/vi/layer/vi_layer.cpp11
-rw-r--r--src/core/hle/service/vi/layer/vi_layer.h62
-rw-r--r--src/core/hle/service/vi/vi.cpp737
-rw-r--r--src/core/hle/service/vi/vi.h15
-rw-r--r--src/core/hle/service/vi/vi_m.cpp14
-rw-r--r--src/core/hle/service/vi/vi_m.h12
-rw-r--r--src/core/hle/service/vi/vi_results.h13
-rw-r--r--src/core/hle/service/vi/vi_s.cpp14
-rw-r--r--src/core/hle/service/vi/vi_s.h12
-rw-r--r--src/core/hle/service/vi/vi_u.cpp14
-rw-r--r--src/core/hle/service/vi/vi_u.h12
-rw-r--r--src/core/hle/service/wlan/wlan.cpp5
-rw-r--r--src/core/hle/service/wlan/wlan.h5
-rw-r--r--src/core/internal_network/network.cpp639
-rw-r--r--src/core/internal_network/network.h84
-rw-r--r--src/core/internal_network/network_interface.cpp219
-rw-r--r--src/core/internal_network/network_interface.h29
-rw-r--r--src/core/internal_network/socket_proxy.cpp296
-rw-r--r--src/core/internal_network/socket_proxy.h97
-rw-r--r--src/core/internal_network/sockets.h174
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp5
-rw-r--r--src/core/loader/deconstructed_rom_directory.h5
-rw-r--r--src/core/loader/elf.cpp414
-rw-r--r--src/core/loader/elf.h36
-rw-r--r--src/core/loader/kip.cpp7
-rw-r--r--src/core/loader/kip.h5
-rw-r--r--src/core/loader/loader.cpp23
-rw-r--r--src/core/loader/loader.h6
-rw-r--r--src/core/loader/nax.cpp5
-rw-r--r--src/core/loader/nax.h5
-rw-r--r--src/core/loader/nca.cpp5
-rw-r--r--src/core/loader/nca.h5
-rw-r--r--src/core/loader/nro.cpp7
-rw-r--r--src/core/loader/nro.h5
-rw-r--r--src/core/loader/nso.cpp16
-rw-r--r--src/core/loader/nso.h5
-rw-r--r--src/core/loader/nsp.cpp5
-rw-r--r--src/core/loader/nsp.h5
-rw-r--r--src/core/loader/xci.cpp5
-rw-r--r--src/core/loader/xci.h5
-rw-r--r--src/core/memory.cpp176
-rw-r--r--src/core/memory.h47
-rw-r--r--src/core/memory/cheat_engine.cpp13
-rw-r--r--src/core/memory/cheat_engine.h5
-rw-r--r--src/core/memory/dmnt_cheat_types.h25
-rw-r--r--src/core/memory/dmnt_cheat_vm.cpp25
-rw-r--r--src/core/memory/dmnt_cheat_vm.h25
-rw-r--r--src/core/network/network.cpp634
-rw-r--r--src/core/network/network.h118
-rw-r--r--src/core/network/network_interface.cpp210
-rw-r--r--src/core/network/network_interface.h29
-rw-r--r--src/core/network/sockets.h94
-rw-r--r--src/core/perf_stats.cpp15
-rw-r--r--src/core/perf_stats.h5
-rw-r--r--src/core/reporter.cpp14
-rw-r--r--src/core/reporter.h11
-rw-r--r--src/core/telemetry_session.cpp5
-rw-r--r--src/core/telemetry_session.h5
-rw-r--r--src/core/tools/freezer.cpp28
-rw-r--r--src/core/tools/freezer.h5
-rw-r--r--src/dedicated_room/CMakeLists.txt27
-rw-r--r--src/dedicated_room/yuzu_room.cpp393
-rw-r--r--src/dedicated_room/yuzu_room.rc20
-rw-r--r--src/input_common/CMakeLists.txt8
-rw-r--r--src/input_common/drivers/camera.cpp82
-rw-r--r--src/input_common/drivers/camera.h29
-rw-r--r--src/input_common/drivers/gc_adapter.cpp25
-rw-r--r--src/input_common/drivers/gc_adapter.h8
-rw-r--r--src/input_common/drivers/keyboard.cpp5
-rw-r--r--src/input_common/drivers/keyboard.h5
-rw-r--r--src/input_common/drivers/mouse.cpp7
-rw-r--r--src/input_common/drivers/mouse.h5
-rw-r--r--src/input_common/drivers/sdl_driver.cpp167
-rw-r--r--src/input_common/drivers/sdl_driver.h35
-rw-r--r--src/input_common/drivers/tas_input.cpp5
-rw-r--r--src/input_common/drivers/tas_input.h5
-rw-r--r--src/input_common/drivers/touch_screen.cpp94
-rw-r--r--src/input_common/drivers/touch_screen.h57
-rw-r--r--src/input_common/drivers/udp_client.cpp35
-rw-r--r--src/input_common/drivers/udp_client.h9
-rw-r--r--src/input_common/drivers/virtual_amiibo.cpp101
-rw-r--r--src/input_common/drivers/virtual_amiibo.h61
-rw-r--r--src/input_common/helpers/stick_from_buttons.cpp5
-rw-r--r--src/input_common/helpers/stick_from_buttons.h5
-rw-r--r--src/input_common/helpers/touch_from_buttons.cpp5
-rw-r--r--src/input_common/helpers/touch_from_buttons.h5
-rw-r--r--src/input_common/helpers/udp_protocol.cpp5
-rw-r--r--src/input_common/helpers/udp_protocol.h17
-rw-r--r--src/input_common/input_engine.cpp135
-rw-r--r--src/input_common/input_engine.h64
-rw-r--r--src/input_common/input_mapping.cpp5
-rw-r--r--src/input_common/input_mapping.h5
-rw-r--r--src/input_common/input_poller.cpp134
-rw-r--r--src/input_common/input_poller.h26
-rw-r--r--src/input_common/main.cpp77
-rw-r--r--src/input_common/main.h24
-rw-r--r--src/network/CMakeLists.txt25
-rw-r--r--src/network/announce_multiplayer_session.cpp164
-rw-r--r--src/network/announce_multiplayer_session.h98
-rw-r--r--src/network/network.cpp50
-rw-r--r--src/network/network.h33
-rw-r--r--src/network/packet.cpp262
-rw-r--r--src/network/packet.h165
-rw-r--r--src/network/room.cpp1143
-rw-r--r--src/network/room.h148
-rw-r--r--src/network/room_member.cpp766
-rw-r--r--src/network/room_member.h337
-rw-r--r--src/network/verify_user.cpp17
-rw-r--r--src/network/verify_user.h45
-rw-r--r--src/shader_recompiler/CMakeLists.txt6
-rw-r--r--src/shader_recompiler/backend/bindings.h5
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm.cpp7
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm.h5
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_barriers.cpp5
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp5
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp5
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp30
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_control_flow.cpp5
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_convert.cpp7
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_floating_point.cpp7
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_image.cpp7
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_instructions.h5
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_integer.cpp5
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_logical.cpp5
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp7
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_not_implemented.cpp8
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_select.cpp5
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_shared_memory.cpp5
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_special.cpp5
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_undefined.cpp5
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_warp.cpp5
-rw-r--r--src/shader_recompiler/backend/glasm/glasm_emit_context.cpp7
-rw-r--r--src/shader_recompiler/backend/glasm/glasm_emit_context.h5
-rw-r--r--src/shader_recompiler/backend/glasm/reg_alloc.cpp8
-rw-r--r--src/shader_recompiler/backend/glasm/reg_alloc.h5
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl.cpp7
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl.h5
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_atomic.cpp5
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_barriers.cpp6
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp5
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_composite.cpp5
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp30
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_control_flow.cpp7
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_convert.cpp5
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_floating_point.cpp5
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_image.cpp6
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_instructions.h5
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_integer.cpp7
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_logical.cpp7
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_memory.cpp5
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_not_implemented.cpp5
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_select.cpp5
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_shared_memory.cpp7
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_special.cpp7
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_undefined.cpp7
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp5
-rw-r--r--src/shader_recompiler/backend/glsl/glsl_emit_context.cpp25
-rw-r--r--src/shader_recompiler/backend/glsl/glsl_emit_context.h6
-rw-r--r--src/shader_recompiler/backend/glsl/var_alloc.cpp5
-rw-r--r--src/shader_recompiler/backend/glsl/var_alloc.h5
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv.cpp5
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv.h7
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp7
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_barriers.cpp7
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp6
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_composite.cpp7
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp66
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_control_flow.cpp6
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp6
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp6
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_image.cpp6
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp6
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_instructions.h5
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp6
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_logical.cpp6
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp5
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_select.cpp6
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp6
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_special.cpp5
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_undefined.cpp6
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp6
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp73
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.h14
-rw-r--r--src/shader_recompiler/environment.h5
-rw-r--r--src/shader_recompiler/exception.h6
-rw-r--r--src/shader_recompiler/frontend/ir/abstract_syntax_list.h5
-rw-r--r--src/shader_recompiler/frontend/ir/attribute.cpp5
-rw-r--r--src/shader_recompiler/frontend/ir/attribute.h5
-rw-r--r--src/shader_recompiler/frontend/ir/basic_block.cpp7
-rw-r--r--src/shader_recompiler/frontend/ir/basic_block.h5
-rw-r--r--src/shader_recompiler/frontend/ir/breadth_first_search.h5
-rw-r--r--src/shader_recompiler/frontend/ir/condition.cpp5
-rw-r--r--src/shader_recompiler/frontend/ir/condition.h6
-rw-r--r--src/shader_recompiler/frontend/ir/flow_test.cpp5
-rw-r--r--src/shader_recompiler/frontend/ir/flow_test.h5
-rw-r--r--src/shader_recompiler/frontend/ir/ir_emitter.cpp10
-rw-r--r--src/shader_recompiler/frontend/ir/ir_emitter.h7
-rw-r--r--src/shader_recompiler/frontend/ir/microinstruction.cpp10
-rw-r--r--src/shader_recompiler/frontend/ir/modifiers.h5
-rw-r--r--src/shader_recompiler/frontend/ir/opcodes.cpp7
-rw-r--r--src/shader_recompiler/frontend/ir/opcodes.h8
-rw-r--r--src/shader_recompiler/frontend/ir/opcodes.inc5
-rw-r--r--src/shader_recompiler/frontend/ir/patch.cpp5
-rw-r--r--src/shader_recompiler/frontend/ir/patch.h5
-rw-r--r--src/shader_recompiler/frontend/ir/post_order.cpp5
-rw-r--r--src/shader_recompiler/frontend/ir/post_order.h5
-rw-r--r--src/shader_recompiler/frontend/ir/pred.h5
-rw-r--r--src/shader_recompiler/frontend/ir/program.cpp5
-rw-r--r--src/shader_recompiler/frontend/ir/program.h5
-rw-r--r--src/shader_recompiler/frontend/ir/reg.h5
-rw-r--r--src/shader_recompiler/frontend/ir/type.cpp5
-rw-r--r--src/shader_recompiler/frontend/ir/type.h5
-rw-r--r--src/shader_recompiler/frontend/ir/value.cpp6
-rw-r--r--src/shader_recompiler/frontend/ir/value.h9
-rw-r--r--src/shader_recompiler/frontend/maxwell/control_flow.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/control_flow.h9
-rw-r--r--src/shader_recompiler/frontend/maxwell/decode.cpp6
-rw-r--r--src/shader_recompiler/frontend/maxwell/decode.h5
-rw-r--r--src/shader_recompiler/frontend/maxwell/indirect_branch_table_track.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/indirect_branch_table_track.h6
-rw-r--r--src/shader_recompiler/frontend/maxwell/instruction.h6
-rw-r--r--src/shader_recompiler/frontend/maxwell/location.h8
-rw-r--r--src/shader_recompiler/frontend/maxwell/maxwell.inc5
-rw-r--r--src/shader_recompiler/frontend/maxwell/opcodes.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/opcodes.h7
-rw-r--r--src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp19
-rw-r--r--src/shader_recompiler/frontend/maxwell/structured_control_flow.h5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/atomic_operations_global_memory.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/atomic_operations_shared_memory.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/attribute_memory_to_physical.cpp6
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/barrier_operations.cpp7
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/bitfield_extract.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/bitfield_insert.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/branch_indirect.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/common_encoding.h5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/common_funcs.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/common_funcs.h6
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/condition_code_set.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/double_add.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/double_compare_and_set.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/double_fused_multiply_add.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/double_min_max.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/double_multiply.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/double_set_predicate.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/exit_program.cpp10
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/find_leading_one.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_add.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_compare.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_compare_and_set.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_floating_point.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_integer.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_fused_multiply_add.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_min_max.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multi_function.cpp6
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multiply.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_range_reduction.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_set_predicate.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_add.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_fused_multiply_add.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.h7
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_multiply.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_set.cpp6
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_set_predicate.cpp6
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/impl.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/impl.h5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_add.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_add_three_input.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_compare.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_compare_and_set.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_floating_point_conversion.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_funnel_shift.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_minimum_maximum.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_popcount.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_scaled_add.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_set_predicate.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_left.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_right.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_short_multiply_add.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/integer_to_integer_conversion.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/internal_stage_buffer_entry_read.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/load_constant.cpp19
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/load_constant.h5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/load_effective_address.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp6
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/load_store_local_shared.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/load_store_memory.cpp6
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input.cpp585
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input_lut3.py90
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/move_predicate_to_register.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp6
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/move_register_to_predicate.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/output_geometry.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/pixel_load.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/predicate_set_predicate.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/predicate_set_register.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/select_source_with_predicate.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/surface_atomic_operations.cpp8
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/surface_load_store.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch_swizzled.cpp7
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_gather.cpp7
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_gather_swizzled.cpp7
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_gradient.cpp7
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_load.cpp9
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_load_swizzled.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_mipmap_level.cpp9
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_query.cpp6
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/video_helper.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/video_helper.h5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/video_minimum_maximum.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/video_multiply_add.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/video_set_predicate.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/vote.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/warp_shuffle.cpp7
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/translate.cpp5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/translate.h5
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate_program.cpp9
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate_program.h10
-rw-r--r--src/shader_recompiler/host_translate_info.h5
-rw-r--r--src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp86
-rw-r--r--src/shader_recompiler/ir_opt/constant_propagation_pass.cpp6
-rw-r--r--src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp103
-rw-r--r--src/shader_recompiler/ir_opt/dual_vertex_pass.cpp5
-rw-r--r--src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp11
-rw-r--r--src/shader_recompiler/ir_opt/identity_removal_pass.cpp5
-rw-r--r--src/shader_recompiler/ir_opt/lower_fp16_to_fp32.cpp8
-rw-r--r--src/shader_recompiler/ir_opt/lower_int64_to_int32.cpp5
-rw-r--r--src/shader_recompiler/ir_opt/passes.h8
-rw-r--r--src/shader_recompiler/ir_opt/rescaling_pass.cpp42
-rw-r--r--src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp6
-rw-r--r--src/shader_recompiler/ir_opt/texture_pass.cpp126
-rw-r--r--src/shader_recompiler/ir_opt/verification_pass.cpp7
-rw-r--r--src/shader_recompiler/object_pool.h5
-rw-r--r--src/shader_recompiler/profile.h5
-rw-r--r--src/shader_recompiler/program_header.h10
-rw-r--r--src/shader_recompiler/runtime_info.h6
-rw-r--r--src/shader_recompiler/shader_info.h15
-rw-r--r--src/shader_recompiler/stage.h5
-rw-r--r--src/shader_recompiler/varying_state.h5
-rw-r--r--src/tests/CMakeLists.txt5
-rw-r--r--src/tests/common/bit_field.cpp5
-rw-r--r--src/tests/common/cityhash.cpp5
-rw-r--r--src/tests/common/fibers.cpp128
-rw-r--r--src/tests/common/host_memory.cpp5
-rw-r--r--src/tests/common/param_package.cpp5
-rw-r--r--src/tests/common/ring_buffer.cpp5
-rw-r--r--src/tests/common/unique_function.cpp5
-rw-r--r--src/tests/core/core_timing.cpp10
-rw-r--r--src/tests/core/internal_network/network.cpp27
-rw-r--r--src/tests/core/network/network.cpp28
-rw-r--r--src/tests/input_common/calibration_configuration_job.cpp5
-rw-r--r--src/tests/tests.cpp5
-rw-r--r--src/tests/video_core/buffer_base.cpp12
-rw-r--r--src/video_core/CMakeLists.txt63
-rw-r--r--src/video_core/buffer_cache/buffer_base.h13
-rw-r--r--src/video_core/buffer_cache/buffer_cache.cpp5
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h259
-rw-r--r--src/video_core/cdma_pusher.cpp50
-rw-r--r--src/video_core/cdma_pusher.h24
-rw-r--r--src/video_core/command_classes/codecs/codec.cpp305
-rw-r--r--src/video_core/command_classes/codecs/codec.h82
-rw-r--r--src/video_core/command_classes/codecs/h264.cpp294
-rw-r--r--src/video_core/command_classes/codecs/h264.h190
-rw-r--r--src/video_core/command_classes/codecs/vp8.cpp55
-rw-r--r--src/video_core/command_classes/codecs/vp8.h74
-rw-r--r--src/video_core/command_classes/codecs/vp9.cpp947
-rw-r--r--src/video_core/command_classes/codecs/vp9.h193
-rw-r--r--src/video_core/command_classes/codecs/vp9_types.h308
-rw-r--r--src/video_core/command_classes/host1x.cpp30
-rw-r--r--src/video_core/command_classes/host1x.h37
-rw-r--r--src/video_core/command_classes/nvdec.cpp48
-rw-r--r--src/video_core/command_classes/nvdec.h34
-rw-r--r--src/video_core/command_classes/nvdec_common.h98
-rw-r--r--src/video_core/command_classes/sync_manager.cpp60
-rw-r--r--src/video_core/command_classes/sync_manager.h64
-rw-r--r--src/video_core/command_classes/vic.cpp239
-rw-r--r--src/video_core/command_classes/vic.h62
-rw-r--r--src/video_core/compatible_formats.cpp11
-rw-r--r--src/video_core/compatible_formats.h5
-rw-r--r--src/video_core/control/channel_state.cpp40
-rw-r--r--src/video_core/control/channel_state.h68
-rw-r--r--src/video_core/control/channel_state_cache.cpp14
-rw-r--r--src/video_core/control/channel_state_cache.h101
-rw-r--r--src/video_core/control/channel_state_cache.inc86
-rw-r--r--src/video_core/control/scheduler.cpp32
-rw-r--r--src/video_core/control/scheduler.h37
-rw-r--r--src/video_core/delayed_destruction_ring.h5
-rw-r--r--src/video_core/dirty_flags.cpp5
-rw-r--r--src/video_core/dirty_flags.h5
-rw-r--r--src/video_core/dma_pusher.cpp32
-rw-r--r--src/video_core/dma_pusher.h44
-rw-r--r--src/video_core/engines/const_buffer_info.h5
-rw-r--r--src/video_core/engines/engine_interface.h5
-rw-r--r--src/video_core/engines/engine_upload.cpp51
-rw-r--r--src/video_core/engines/engine_upload.h11
-rw-r--r--src/video_core/engines/fermi_2d.cpp5
-rw-r--r--src/video_core/engines/fermi_2d.h6
-rw-r--r--src/video_core/engines/kepler_compute.cpp19
-rw-r--r--src/video_core/engines/kepler_compute.h6
-rw-r--r--src/video_core/engines/kepler_memory.cpp20
-rw-r--r--src/video_core/engines/kepler_memory.h7
-rw-r--r--src/video_core/engines/maxwell_3d.cpp168
-rw-r--r--src/video_core/engines/maxwell_3d.h75
-rw-r--r--src/video_core/engines/maxwell_dma.cpp125
-rw-r--r--src/video_core/engines/maxwell_dma.h15
-rw-r--r--src/video_core/engines/puller.cpp306
-rw-r--r--src/video_core/engines/puller.h177
-rw-r--r--src/video_core/fence_manager.h111
-rw-r--r--src/video_core/framebuffer_config.h34
-rw-r--r--src/video_core/gpu.cpp711
-rw-r--r--src/video_core/gpu.h99
-rw-r--r--src/video_core/gpu_thread.cpp33
-rw-r--r--src/video_core/gpu_thread.h21
-rw-r--r--src/video_core/host1x/codecs/codec.cpp310
-rw-r--r--src/video_core/host1x/codecs/codec.h84
-rw-r--r--src/video_core/host1x/codecs/h264.cpp278
-rw-r--r--src/video_core/host1x/codecs/h264.h177
-rw-r--r--src/video_core/host1x/codecs/vp8.cpp53
-rw-r--r--src/video_core/host1x/codecs/vp8.h78
-rw-r--r--src/video_core/host1x/codecs/vp9.cpp947
-rw-r--r--src/video_core/host1x/codecs/vp9.h198
-rw-r--r--src/video_core/host1x/codecs/vp9_types.h305
-rw-r--r--src/video_core/host1x/control.cpp33
-rw-r--r--src/video_core/host1x/control.h40
-rw-r--r--src/video_core/host1x/host1x.cpp17
-rw-r--r--src/video_core/host1x/host1x.h57
-rw-r--r--src/video_core/host1x/nvdec.cpp48
-rw-r--r--src/video_core/host1x/nvdec.h39
-rw-r--r--src/video_core/host1x/nvdec_common.h97
-rw-r--r--src/video_core/host1x/sync_manager.cpp50
-rw-r--r--src/video_core/host1x/sync_manager.h53
-rw-r--r--src/video_core/host1x/syncpoint_manager.cpp96
-rw-r--r--src/video_core/host1x/syncpoint_manager.h98
-rw-r--r--src/video_core/host1x/vic.cpp244
-rw-r--r--src/video_core/host1x/vic.h66
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt5
-rw-r--r--src/video_core/host_shaders/StringShaderHeader.cmake3
-rw-r--r--src/video_core/host_shaders/astc_decoder.comp7
-rw-r--r--src/video_core/host_shaders/block_linear_unswizzle_2d.comp5
-rw-r--r--src/video_core/host_shaders/block_linear_unswizzle_3d.comp5
-rw-r--r--src/video_core/host_shaders/convert_abgr8_to_d24s8.frag5
-rw-r--r--src/video_core/host_shaders/convert_d24s8_to_abgr8.frag5
-rw-r--r--src/video_core/host_shaders/convert_depth_to_float.frag5
-rw-r--r--src/video_core/host_shaders/convert_float_to_depth.frag5
-rw-r--r--src/video_core/host_shaders/convert_s8d24_to_abgr8.frag22
-rw-r--r--src/video_core/host_shaders/fidelityfx_fsr.comp5
-rw-r--r--src/video_core/host_shaders/full_screen_triangle.vert5
-rw-r--r--src/video_core/host_shaders/fxaa.frag5
-rw-r--r--src/video_core/host_shaders/fxaa.vert5
-rw-r--r--src/video_core/host_shaders/opengl_convert_s8d24.comp17
-rw-r--r--src/video_core/host_shaders/opengl_copy_bc4.comp5
-rw-r--r--src/video_core/host_shaders/opengl_present.frag5
-rw-r--r--src/video_core/host_shaders/opengl_present.vert5
-rw-r--r--src/video_core/host_shaders/opengl_present_scaleforce.frag23
-rw-r--r--src/video_core/host_shaders/pitch_unswizzle.comp5
-rw-r--r--src/video_core/host_shaders/present_bicubic.frag5
-rw-r--r--src/video_core/host_shaders/present_gaussian.frag5
-rw-r--r--src/video_core/host_shaders/source_shader.h.in3
-rw-r--r--src/video_core/host_shaders/vulkan_blit_color_float.frag5
-rw-r--r--src/video_core/host_shaders/vulkan_blit_depth_stencil.frag5
-rw-r--r--src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.comp5
-rw-r--r--src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.comp5
-rw-r--r--src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.comp5
-rw-r--r--src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.comp5
-rw-r--r--src/video_core/host_shaders/vulkan_present.frag5
-rw-r--r--src/video_core/host_shaders/vulkan_present.vert5
-rw-r--r--src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag3
-rw-r--r--src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag3
-rw-r--r--src/video_core/host_shaders/vulkan_quad_indexed.comp5
-rw-r--r--src/video_core/host_shaders/vulkan_uint8.comp5
-rw-r--r--src/video_core/macro/macro.cpp40
-rw-r--r--src/video_core/macro/macro.h8
-rw-r--r--src/video_core/macro/macro_hle.cpp68
-rw-r--r--src/video_core/macro/macro_hle.h5
-rw-r--r--src/video_core/macro/macro_interpreter.cpp6
-rw-r--r--src/video_core/macro/macro_interpreter.h5
-rw-r--r--src/video_core/macro/macro_jit_x64.cpp93
-rw-r--r--src/video_core/macro/macro_jit_x64.h5
-rw-r--r--src/video_core/memory_manager.cpp766
-rw-r--r--src/video_core/memory_manager.h179
-rw-r--r--src/video_core/query_cache.h41
-rw-r--r--src/video_core/rasterizer_accelerated.cpp22
-rw-r--r--src/video_core/rasterizer_accelerated.h5
-rw-r--r--src/video_core/rasterizer_interface.h25
-rw-r--r--src/video_core/renderer_base.cpp10
-rw-r--r--src/video_core/renderer_base.h8
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp21
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h19
-rw-r--r--src/video_core/renderer_opengl/gl_compute_pipeline.cpp25
-rw-r--r--src/video_core/renderer_opengl/gl_compute_pipeline.h22
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp18
-rw-r--r--src/video_core/renderer_opengl/gl_device.h12
-rw-r--r--src/video_core/renderer_opengl/gl_fence_manager.cpp21
-rw-r--r--src/video_core/renderer_opengl/gl_fence_manager.h11
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.cpp84
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.h29
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.cpp13
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.h8
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp242
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h37
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.cpp7
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.h5
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp64
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h19
-rw-r--r--src/video_core/renderer_opengl/gl_shader_context.h5
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.cpp5
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.h5
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.cpp7
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.h9
-rw-r--r--src/video_core/renderer_opengl/gl_state_tracker.cpp22
-rw-r--r--src/video_core/renderer_opengl/gl_state_tracker.h89
-rw-r--r--src/video_core/renderer_opengl/gl_stream_buffer.cpp5
-rw-r--r--src/video_core/renderer_opengl/gl_stream_buffer.h6
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp107
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h21
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache_base.cpp5
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h24
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp46
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h13
-rw-r--r--src/video_core/renderer_opengl/util_shaders.cpp31
-rw-r--r--src/video_core/renderer_opengl/util_shaders.h8
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp23
-rw-r--r--src/video_core/renderer_vulkan/blit_image.h17
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.cpp8
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.h5
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp391
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.h9
-rw-r--r--src/video_core/renderer_vulkan/pipeline_helper.h9
-rw-r--r--src/video_core/renderer_vulkan/pipeline_statistics.cpp9
-rw-r--r--src/video_core/renderer_vulkan/pipeline_statistics.h5
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp21
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h14
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp141
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.h30
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp25
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h21
-rw-r--r--src/video_core/renderer_vulkan/vk_command_pool.cpp5
-rw-r--r--src/video_core/renderer_vulkan/vk_command_pool.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.cpp80
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.h33
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.cpp15
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.h14
-rw-r--r--src/video_core/renderer_vulkan/vk_descriptor_pool.cpp7
-rw-r--r--src/video_core/renderer_vulkan/vk_descriptor_pool.h9
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.cpp34
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.h32
-rw-r--r--src/video_core/renderer_vulkan/vk_fsr.cpp7
-rw-r--r--src/video_core/renderer_vulkan/vk_fsr.h9
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp29
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.h41
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.cpp5
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp65
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.h24
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp22
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.h36
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp150
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h52
-rw-r--r--src/video_core/renderer_vulkan/vk_render_pass_cache.cpp7
-rw-r--r--src/video_core/renderer_vulkan/vk_render_pass_cache.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_pool.cpp5
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_pool.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp52
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h18
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_util.cpp7
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_util.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp93
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.h14
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.cpp24
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.h33
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp57
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.h17
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp153
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h22
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache_base.cpp5
-rw-r--r--src/video_core/renderer_vulkan/vk_update_descriptor.cpp14
-rw-r--r--src/video_core/renderer_vulkan/vk_update_descriptor.h16
-rw-r--r--src/video_core/shader_cache.cpp52
-rw-r--r--src/video_core/shader_cache.h24
-rw-r--r--src/video_core/shader_environment.cpp18
-rw-r--r--src/video_core/shader_environment.h5
-rw-r--r--src/video_core/shader_notify.cpp6
-rw-r--r--src/video_core/shader_notify.h6
-rw-r--r--src/video_core/surface.cpp23
-rw-r--r--src/video_core/surface.h33
-rw-r--r--src/video_core/texture_cache/accelerated_swizzle.cpp5
-rw-r--r--src/video_core/texture_cache/accelerated_swizzle.h5
-rw-r--r--src/video_core/texture_cache/decode_bc4.cpp5
-rw-r--r--src/video_core/texture_cache/decode_bc4.h5
-rw-r--r--src/video_core/texture_cache/descriptor_table.h6
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp17
-rw-r--r--src/video_core/texture_cache/format_lookup_table.h5
-rw-r--r--src/video_core/texture_cache/formatter.cpp5
-rw-r--r--src/video_core/texture_cache/formatter.h15
-rw-r--r--src/video_core/texture_cache/image_base.cpp18
-rw-r--r--src/video_core/texture_cache/image_base.h15
-rw-r--r--src/video_core/texture_cache/image_info.cpp7
-rw-r--r--src/video_core/texture_cache/image_info.h5
-rw-r--r--src/video_core/texture_cache/image_view_base.cpp5
-rw-r--r--src/video_core/texture_cache/image_view_base.h5
-rw-r--r--src/video_core/texture_cache/image_view_info.cpp7
-rw-r--r--src/video_core/texture_cache/image_view_info.h5
-rw-r--r--src/video_core/texture_cache/render_targets.h7
-rw-r--r--src/video_core/texture_cache/samples_helper.h9
-rw-r--r--src/video_core/texture_cache/slot_vector.h7
-rw-r--r--src/video_core/texture_cache/texture_cache.cpp15
-rw-r--r--src/video_core/texture_cache/texture_cache.h321
-rw-r--r--src/video_core/texture_cache/texture_cache_base.h104
-rw-r--r--src/video_core/texture_cache/types.h5
-rw-r--r--src/video_core/texture_cache/util.cpp29
-rw-r--r--src/video_core/texture_cache/util.h7
-rw-r--r--src/video_core/textures/astc.cpp76
-rw-r--r--src/video_core/textures/astc.h8
-rw-r--r--src/video_core/textures/decoders.cpp269
-rw-r--r--src/video_core/textures/decoders.h41
-rw-r--r--src/video_core/textures/texture.cpp6
-rw-r--r--src/video_core/textures/texture.h5
-rw-r--r--src/video_core/transform_feedback.cpp5
-rw-r--r--src/video_core/transform_feedback.h5
-rw-r--r--src/video_core/video_core.cpp6
-rw-r--r--src/video_core/video_core.h5
-rw-r--r--src/video_core/vulkan_common/nsight_aftermath_tracker.cpp5
-rw-r--r--src/video_core/vulkan_common/nsight_aftermath_tracker.h19
-rw-r--r--src/video_core/vulkan_common/vulkan_debug_callback.cpp5
-rw-r--r--src/video_core/vulkan_common/vulkan_debug_callback.h5
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp360
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h22
-rw-r--r--src/video_core/vulkan_common/vulkan_instance.cpp7
-rw-r--r--src/video_core/vulkan_common/vulkan_instance.h5
-rw-r--r--src/video_core/vulkan_common/vulkan_library.cpp10
-rw-r--r--src/video_core/vulkan_common/vulkan_library.h5
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp13
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.h6
-rw-r--r--src/video_core/vulkan_common/vulkan_surface.cpp5
-rw-r--r--src/video_core/vulkan_common/vulkan_surface.h5
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp22
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h30
-rw-r--r--src/web_service/CMakeLists.txt9
-rw-r--r--src/web_service/announce_room_json.cpp145
-rw-r--r--src/web_service/announce_room_json.h41
-rw-r--r--src/web_service/telemetry_json.cpp9
-rw-r--r--src/web_service/telemetry_json.h5
-rw-r--r--src/web_service/verify_login.cpp5
-rw-r--r--src/web_service/verify_login.h5
-rw-r--r--src/web_service/verify_user_jwt.cpp69
-rw-r--r--src/web_service/verify_user_jwt.h26
-rw-r--r--src/web_service/web_backend.cpp33
-rw-r--r--src/web_service/web_backend.h5
-rw-r--r--src/web_service/web_result.h5
-rw-r--r--src/yuzu/CMakeLists.txt94
-rw-r--r--src/yuzu/Info.plist6
-rw-r--r--src/yuzu/about_dialog.cpp11
-rw-r--r--src/yuzu/about_dialog.h5
-rw-r--r--src/yuzu/aboutdialog.ui23
-rw-r--r--src/yuzu/applets/qt_controller.cpp12
-rw-r--r--src/yuzu/applets/qt_controller.h5
-rw-r--r--src/yuzu/applets/qt_error.cpp11
-rw-r--r--src/yuzu/applets/qt_error.h11
-rw-r--r--src/yuzu/applets/qt_profile_select.cpp7
-rw-r--r--src/yuzu/applets/qt_profile_select.h7
-rw-r--r--src/yuzu/applets/qt_software_keyboard.cpp71
-rw-r--r--src/yuzu/applets/qt_software_keyboard.h7
-rw-r--r--src/yuzu/applets/qt_software_keyboard.ui38
-rw-r--r--src/yuzu/applets/qt_web_browser.cpp27
-rw-r--r--src/yuzu/applets/qt_web_browser.h6
-rw-r--r--src/yuzu/applets/qt_web_browser_scripts.h5
-rw-r--r--src/yuzu/bootmanager.cpp185
-rw-r--r--src/yuzu/bootmanager.h46
-rw-r--r--src/yuzu/compatdb.cpp5
-rw-r--r--src/yuzu/compatdb.h5
-rw-r--r--src/yuzu/compatdb.ui2
-rw-r--r--src/yuzu/compatibility_list.cpp5
-rw-r--r--src/yuzu/compatibility_list.h5
-rw-r--r--src/yuzu/configuration/config.cpp223
-rw-r--r--src/yuzu/configuration/config.h32
-rw-r--r--src/yuzu/configuration/configuration_shared.cpp12
-rw-r--r--src/yuzu/configuration/configuration_shared.h22
-rw-r--r--src/yuzu/configuration/configure_audio.cpp102
-rw-r--r--src/yuzu/configuration/configure_audio.h9
-rw-r--r--src/yuzu/configuration/configure_audio.ui32
-rw-r--r--src/yuzu/configuration/configure_camera.cpp156
-rw-r--r--src/yuzu/configuration/configure_camera.h54
-rw-r--r--src/yuzu/configuration/configure_camera.ui170
-rw-r--r--src/yuzu/configuration/configure_cpu.cpp18
-rw-r--r--src/yuzu/configuration/configure_cpu.h7
-rw-r--r--src/yuzu/configuration/configure_cpu.ui17
-rw-r--r--src/yuzu/configuration/configure_cpu_debug.cpp17
-rw-r--r--src/yuzu/configuration/configure_cpu_debug.h5
-rw-r--r--src/yuzu/configuration/configure_cpu_debug.ui29
-rw-r--r--src/yuzu/configuration/configure_debug.cpp50
-rw-r--r--src/yuzu/configuration/configure_debug.h11
-rw-r--r--src/yuzu/configuration/configure_debug.ui192
-rw-r--r--src/yuzu/configuration/configure_debug_controller.cpp5
-rw-r--r--src/yuzu/configuration/configure_debug_controller.h5
-rw-r--r--src/yuzu/configuration/configure_debug_tab.cpp5
-rw-r--r--src/yuzu/configuration/configure_debug_tab.h5
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp24
-rw-r--r--src/yuzu/configuration/configure_dialog.h10
-rw-r--r--src/yuzu/configuration/configure_filesystem.cpp5
-rw-r--r--src/yuzu/configuration/configure_filesystem.h5
-rw-r--r--src/yuzu/configuration/configure_general.cpp41
-rw-r--r--src/yuzu/configuration/configure_general.h6
-rw-r--r--src/yuzu/configuration/configure_general.ui88
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp15
-rw-r--r--src/yuzu/configuration/configure_graphics.h5
-rw-r--r--src/yuzu/configuration/configure_graphics.ui84
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp13
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.h6
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.ui12
-rw-r--r--src/yuzu/configuration/configure_hotkeys.cpp34
-rw-r--r--src/yuzu/configuration/configure_hotkeys.h5
-rw-r--r--src/yuzu/configuration/configure_input.cpp20
-rw-r--r--src/yuzu/configuration/configure_input.h5
-rw-r--r--src/yuzu/configuration/configure_input.ui2
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp17
-rw-r--r--src/yuzu/configuration/configure_input_advanced.h7
-rw-r--r--src/yuzu/configuration/configure_input_advanced.ui28
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp179
-rw-r--r--src/yuzu/configuration/configure_input_player.h5
-rw-r--r--src/yuzu/configuration/configure_input_player.ui8
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.cpp5
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.h5
-rw-r--r--src/yuzu/configuration/configure_input_profile_dialog.cpp5
-rw-r--r--src/yuzu/configuration/configure_input_profile_dialog.h5
-rw-r--r--src/yuzu/configuration/configure_motion_touch.cpp12
-rw-r--r--src/yuzu/configuration/configure_motion_touch.h6
-rw-r--r--src/yuzu/configuration/configure_motion_touch.ui19
-rw-r--r--src/yuzu/configuration/configure_network.cpp18
-rw-r--r--src/yuzu/configuration/configure_network.h9
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp24
-rw-r--r--src/yuzu/configuration/configure_per_game.h10
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp16
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.h7
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp7
-rw-r--r--src/yuzu/configuration/configure_profile_manager.h5
-rw-r--r--src/yuzu/configuration/configure_ringcon.cpp423
-rw-r--r--src/yuzu/configuration/configure_ringcon.h84
-rw-r--r--src/yuzu/configuration/configure_ringcon.ui278
-rw-r--r--src/yuzu/configuration/configure_system.cpp13
-rw-r--r--src/yuzu/configuration/configure_system.h6
-rw-r--r--src/yuzu/configuration/configure_system.ui3
-rw-r--r--src/yuzu/configuration/configure_tas.cpp5
-rw-r--r--src/yuzu/configuration/configure_tas.h7
-rw-r--r--src/yuzu/configuration/configure_tas.ui3
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.cpp13
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.h7
-rw-r--r--src/yuzu/configuration/configure_touch_widget.h5
-rw-r--r--src/yuzu/configuration/configure_touchscreen_advanced.cpp5
-rw-r--r--src/yuzu/configuration/configure_touchscreen_advanced.h5
-rw-r--r--src/yuzu/configuration/configure_ui.cpp79
-rw-r--r--src/yuzu/configuration/configure_ui.h5
-rw-r--r--src/yuzu/configuration/configure_vibration.cpp11
-rw-r--r--src/yuzu/configuration/configure_vibration.h5
-rw-r--r--src/yuzu/configuration/configure_web.cpp31
-rw-r--r--src/yuzu/configuration/configure_web.h6
-rw-r--r--src/yuzu/configuration/configure_web.ui10
-rw-r--r--src/yuzu/configuration/input_profiles.cpp16
-rw-r--r--src/yuzu/configuration/input_profiles.h10
-rw-r--r--src/yuzu/debugger/console.cpp6
-rw-r--r--src/yuzu/debugger/console.h5
-rw-r--r--src/yuzu/debugger/controller.cpp5
-rw-r--r--src/yuzu/debugger/controller.h5
-rw-r--r--src/yuzu/debugger/profiler.cpp5
-rw-r--r--src/yuzu/debugger/profiler.h5
-rw-r--r--src/yuzu/debugger/wait_tree.cpp23
-rw-r--r--src/yuzu/debugger/wait_tree.h14
-rw-r--r--src/yuzu/discord.h5
-rw-r--r--src/yuzu/discord_impl.cpp5
-rw-r--r--src/yuzu/discord_impl.h5
-rw-r--r--src/yuzu/game_list.cpp83
-rw-r--r--src/yuzu/game_list.h28
-rw-r--r--src/yuzu/game_list_p.h23
-rw-r--r--src/yuzu/game_list_worker.cpp18
-rw-r--r--src/yuzu/game_list_worker.h17
-rw-r--r--src/yuzu/hotkeys.cpp6
-rw-r--r--src/yuzu/hotkeys.h5
-rw-r--r--src/yuzu/install_dialog.cpp6
-rw-r--r--src/yuzu/install_dialog.h5
-rw-r--r--src/yuzu/loading_screen.cpp18
-rw-r--r--src/yuzu/loading_screen.h8
-rw-r--r--src/yuzu/main.cpp647
-rw-r--r--src/yuzu/main.h46
-rw-r--r--src/yuzu/main.ui54
-rw-r--r--src/yuzu/mini_dump.cpp202
-rw-r--r--src/yuzu/mini_dump.h19
-rw-r--r--src/yuzu/multiplayer/chat_room.cpp508
-rw-r--r--src/yuzu/multiplayer/chat_room.h75
-rw-r--r--src/yuzu/multiplayer/chat_room.ui59
-rw-r--r--src/yuzu/multiplayer/client_room.cpp115
-rw-r--r--src/yuzu/multiplayer/client_room.h39
-rw-r--r--src/yuzu/multiplayer/client_room.ui80
-rw-r--r--src/yuzu/multiplayer/direct_connect.cpp143
-rw-r--r--src/yuzu/multiplayer/direct_connect.h49
-rw-r--r--src/yuzu/multiplayer/direct_connect.ui168
-rw-r--r--src/yuzu/multiplayer/host_room.cpp260
-rw-r--r--src/yuzu/multiplayer/host_room.h80
-rw-r--r--src/yuzu/multiplayer/host_room.ui207
-rw-r--r--src/yuzu/multiplayer/lobby.cpp410
-rw-r--r--src/yuzu/multiplayer/lobby.h141
-rw-r--r--src/yuzu/multiplayer/lobby.ui123
-rw-r--r--src/yuzu/multiplayer/lobby_p.h244
-rw-r--r--src/yuzu/multiplayer/message.cpp85
-rw-r--r--src/yuzu/multiplayer/message.h72
-rw-r--r--src/yuzu/multiplayer/moderation_dialog.cpp112
-rw-r--r--src/yuzu/multiplayer/moderation_dialog.h43
-rw-r--r--src/yuzu/multiplayer/moderation_dialog.ui84
-rw-r--r--src/yuzu/multiplayer/state.cpp336
-rw-r--r--src/yuzu/multiplayer/state.h111
-rw-r--r--src/yuzu/multiplayer/validation.h48
-rw-r--r--src/yuzu/startup_checks.cpp158
-rw-r--r--src/yuzu/startup_checks.h20
-rw-r--r--src/yuzu/uisettings.cpp13
-rw-r--r--src/yuzu/uisettings.h70
-rw-r--r--src/yuzu/util/clickable_label.cpp11
-rw-r--r--src/yuzu/util/clickable_label.h21
-rw-r--r--src/yuzu/util/controller_navigation.cpp7
-rw-r--r--src/yuzu/util/controller_navigation.h5
-rw-r--r--src/yuzu/util/limitable_input_dialog.cpp5
-rw-r--r--src/yuzu/util/limitable_input_dialog.h5
-rw-r--r--src/yuzu/util/overlay_dialog.cpp5
-rw-r--r--src/yuzu/util/overlay_dialog.h6
-rw-r--r--src/yuzu/util/sequence_dialog/sequence_dialog.cpp5
-rw-r--r--src/yuzu/util/sequence_dialog/sequence_dialog.h5
-rw-r--r--src/yuzu/util/url_request_interceptor.cpp5
-rw-r--r--src/yuzu/util/url_request_interceptor.h5
-rw-r--r--src/yuzu/util/util.cpp5
-rw-r--r--src/yuzu/util/util.h5
-rw-r--r--src/yuzu/yuzu.qrc5
-rw-r--r--src/yuzu/yuzu.rc3
-rw-r--r--src/yuzu_cmd/CMakeLists.txt5
-rw-r--r--src/yuzu_cmd/config.cpp35
-rw-r--r--src/yuzu_cmd/config.h16
-rw-r--r--src/yuzu_cmd/default_ini.h46
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp30
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h8
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp10
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h7
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp11
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h7
-rw-r--r--src/yuzu_cmd/yuzu.cpp203
-rw-r--r--src/yuzu_cmd/yuzu.rc3
1949 files changed, 90470 insertions, 32522 deletions
diff --git a/src/.clang-format b/src/.clang-format
index 1c6b71b2e..f92771ec3 100644
--- a/src/.clang-format
+++ b/src/.clang-format
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: 2016 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+# SPDX-License-Identifier: GPL-2.0-or-later
+
---
Language: Cpp
# BasedOnStyle: LLVM
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1bdf70b76..3575a3cb3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
# Enable modules to include each other's files
include_directories(.)
@@ -36,7 +39,6 @@ if (MSVC)
# /GT - Supports fiber safety for data allocated using static thread-local storage
add_compile_options(
/MP
- /Zi
/Zm200
/Zo
/permissive-
@@ -65,14 +67,27 @@ if (MSVC)
/we4305 # 'context': truncation from 'type1' to 'type2'
/we4388 # 'expression': signed/unsigned mismatch
/we4389 # 'operator': signed/unsigned mismatch
+ /we4456 # Declaration of 'identifier' hides previous local declaration
+ /we4457 # Declaration of 'identifier' hides function parameter
+ /we4458 # Declaration of 'identifier' hides class member
+ /we4459 # Declaration of 'identifier' hides global declaration
+ /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 (USE_CCACHE)
+ # when caching, we need to use /Z7 to downgrade debug info to use an older but more cachable format
+ add_compile_options(/Z7)
+ else()
+ add_compile_options(/Zi)
+ endif()
+
if (ARCHITECTURE_x86_64)
add_compile_options(/QIntel-jcc-erratum)
endif()
@@ -90,6 +105,7 @@ else()
-Werror=missing-declarations
-Werror=missing-field-initializers
-Werror=reorder
+ -Werror=shadow
-Werror=sign-compare
-Werror=switch
-Werror=uninitialized
@@ -103,14 +119,9 @@ 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")
+ add_compile_options("-fwrapv")
endif()
if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
@@ -149,8 +160,10 @@ add_subdirectory(common)
add_subdirectory(core)
add_subdirectory(audio_core)
add_subdirectory(video_core)
+add_subdirectory(network)
add_subdirectory(input_common)
add_subdirectory(shader_recompiler)
+add_subdirectory(dedicated_room)
if (YUZU_TESTS)
add_subdirectory(tests)
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 090dd19b1..144f1bab2 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -1,58 +1,221 @@
-add_library(audio_core STATIC
- algorithm/filter.cpp
- algorithm/filter.h
- algorithm/interpolate.cpp
- algorithm/interpolate.h
- audio_out.cpp
- audio_out.h
- audio_renderer.cpp
- audio_renderer.h
- behavior_info.cpp
- behavior_info.h
- buffer.h
- codec.cpp
- codec.h
- command_generator.cpp
- command_generator.h
- common.h
- delay_line.cpp
- delay_line.h
- effect_context.cpp
- effect_context.h
- info_updater.cpp
- info_updater.h
- memory_pool.cpp
- memory_pool.h
- mix_context.cpp
- mix_context.h
- null_sink.h
- sink.h
- sink_context.cpp
- sink_context.h
- sink_details.cpp
- sink_details.h
- sink_stream.h
- splitter_context.cpp
- splitter_context.h
- stream.cpp
- stream.h
- time_stretch.cpp
- time_stretch.h
- voice_context.cpp
- voice_context.h
+# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
- $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
- $<$<BOOL:${ENABLE_SDL2}>:sdl2_sink.cpp sdl2_sink.h>
+add_library(audio_core STATIC
+ audio_core.cpp
+ audio_core.h
+ audio_event.h
+ audio_event.cpp
+ audio_render_manager.cpp
+ audio_render_manager.h
+ audio_in_manager.cpp
+ audio_in_manager.h
+ audio_out_manager.cpp
+ audio_out_manager.h
+ audio_manager.cpp
+ audio_manager.h
+ common/audio_renderer_parameter.h
+ common/common.h
+ common/feature_support.h
+ common/wave_buffer.h
+ common/workbuffer_allocator.h
+ device/audio_buffer.h
+ device/audio_buffers.h
+ device/device_session.cpp
+ device/device_session.h
+ in/audio_in.cpp
+ in/audio_in.h
+ in/audio_in_system.cpp
+ in/audio_in_system.h
+ out/audio_out.cpp
+ out/audio_out.h
+ out/audio_out_system.cpp
+ out/audio_out_system.h
+ renderer/adsp/adsp.cpp
+ renderer/adsp/adsp.h
+ renderer/adsp/audio_renderer.cpp
+ renderer/adsp/audio_renderer.h
+ renderer/adsp/command_buffer.h
+ renderer/adsp/command_list_processor.cpp
+ renderer/adsp/command_list_processor.h
+ renderer/audio_device.cpp
+ renderer/audio_device.h
+ renderer/audio_renderer.h
+ renderer/audio_renderer.cpp
+ renderer/behavior/behavior_info.cpp
+ renderer/behavior/behavior_info.h
+ renderer/behavior/info_updater.cpp
+ renderer/behavior/info_updater.h
+ renderer/command/data_source/adpcm.cpp
+ renderer/command/data_source/adpcm.h
+ renderer/command/data_source/decode.cpp
+ renderer/command/data_source/decode.h
+ renderer/command/data_source/pcm_float.cpp
+ renderer/command/data_source/pcm_float.h
+ renderer/command/data_source/pcm_int16.cpp
+ renderer/command/data_source/pcm_int16.h
+ renderer/command/effect/aux_.cpp
+ renderer/command/effect/aux_.h
+ renderer/command/effect/biquad_filter.cpp
+ renderer/command/effect/biquad_filter.h
+ renderer/command/effect/capture.cpp
+ renderer/command/effect/capture.h
+ renderer/command/effect/compressor.cpp
+ renderer/command/effect/compressor.h
+ renderer/command/effect/delay.cpp
+ renderer/command/effect/delay.h
+ renderer/command/effect/i3dl2_reverb.cpp
+ renderer/command/effect/i3dl2_reverb.h
+ renderer/command/effect/light_limiter.cpp
+ renderer/command/effect/light_limiter.h
+ renderer/command/effect/multi_tap_biquad_filter.cpp
+ renderer/command/effect/multi_tap_biquad_filter.h
+ renderer/command/effect/reverb.cpp
+ renderer/command/effect/reverb.h
+ renderer/command/mix/clear_mix.cpp
+ renderer/command/mix/clear_mix.h
+ renderer/command/mix/copy_mix.cpp
+ renderer/command/mix/copy_mix.h
+ renderer/command/mix/depop_for_mix_buffers.cpp
+ renderer/command/mix/depop_for_mix_buffers.h
+ renderer/command/mix/depop_prepare.cpp
+ renderer/command/mix/depop_prepare.h
+ renderer/command/mix/mix.cpp
+ renderer/command/mix/mix.h
+ renderer/command/mix/mix_ramp.cpp
+ renderer/command/mix/mix_ramp.h
+ renderer/command/mix/mix_ramp_grouped.cpp
+ renderer/command/mix/mix_ramp_grouped.h
+ renderer/command/mix/volume.cpp
+ renderer/command/mix/volume.h
+ renderer/command/mix/volume_ramp.cpp
+ renderer/command/mix/volume_ramp.h
+ renderer/command/performance/performance.cpp
+ renderer/command/performance/performance.h
+ renderer/command/resample/downmix_6ch_to_2ch.cpp
+ renderer/command/resample/downmix_6ch_to_2ch.h
+ renderer/command/resample/resample.h
+ renderer/command/resample/resample.cpp
+ renderer/command/resample/upsample.cpp
+ renderer/command/resample/upsample.h
+ renderer/command/sink/device.cpp
+ renderer/command/sink/device.h
+ renderer/command/sink/circular_buffer.cpp
+ renderer/command/sink/circular_buffer.h
+ renderer/command/command_buffer.cpp
+ renderer/command/command_buffer.h
+ renderer/command/command_generator.cpp
+ renderer/command/command_generator.h
+ renderer/command/command_list_header.h
+ renderer/command/command_processing_time_estimator.cpp
+ renderer/command/command_processing_time_estimator.h
+ renderer/command/commands.h
+ renderer/command/icommand.h
+ renderer/effect/aux_.cpp
+ renderer/effect/aux_.h
+ renderer/effect/biquad_filter.cpp
+ renderer/effect/biquad_filter.h
+ renderer/effect/buffer_mixer.cpp
+ renderer/effect/buffer_mixer.h
+ renderer/effect/capture.cpp
+ renderer/effect/capture.h
+ renderer/effect/compressor.cpp
+ renderer/effect/compressor.h
+ renderer/effect/delay.cpp
+ renderer/effect/delay.h
+ renderer/effect/effect_context.cpp
+ renderer/effect/effect_context.h
+ renderer/effect/effect_info_base.h
+ renderer/effect/effect_reset.h
+ renderer/effect/effect_result_state.h
+ renderer/effect/i3dl2.cpp
+ renderer/effect/i3dl2.h
+ renderer/effect/light_limiter.cpp
+ renderer/effect/light_limiter.h
+ renderer/effect/reverb.h
+ renderer/effect/reverb.cpp
+ renderer/mix/mix_context.cpp
+ renderer/mix/mix_context.h
+ renderer/mix/mix_info.cpp
+ renderer/mix/mix_info.h
+ renderer/memory/address_info.h
+ renderer/memory/memory_pool_info.cpp
+ renderer/memory/memory_pool_info.h
+ renderer/memory/pool_mapper.cpp
+ renderer/memory/pool_mapper.h
+ renderer/nodes/bit_array.h
+ renderer/nodes/edge_matrix.cpp
+ renderer/nodes/edge_matrix.h
+ renderer/nodes/node_states.cpp
+ renderer/nodes/node_states.h
+ renderer/performance/detail_aspect.cpp
+ renderer/performance/detail_aspect.h
+ renderer/performance/entry_aspect.cpp
+ renderer/performance/entry_aspect.h
+ renderer/performance/performance_detail.h
+ renderer/performance/performance_entry.h
+ renderer/performance/performance_entry_addresses.h
+ renderer/performance/performance_frame_header.h
+ renderer/performance/performance_manager.cpp
+ renderer/performance/performance_manager.h
+ renderer/sink/circular_buffer_sink_info.cpp
+ renderer/sink/circular_buffer_sink_info.h
+ renderer/sink/device_sink_info.cpp
+ renderer/sink/device_sink_info.h
+ renderer/sink/sink_context.cpp
+ renderer/sink/sink_context.h
+ renderer/sink/sink_info_base.cpp
+ renderer/sink/sink_info_base.h
+ renderer/splitter/splitter_context.cpp
+ renderer/splitter/splitter_context.h
+ renderer/splitter/splitter_destinations_data.cpp
+ renderer/splitter/splitter_destinations_data.h
+ renderer/splitter/splitter_info.cpp
+ renderer/splitter/splitter_info.h
+ renderer/system.cpp
+ renderer/system.h
+ renderer/system_manager.cpp
+ renderer/system_manager.h
+ renderer/upsampler/upsampler_info.h
+ renderer/upsampler/upsampler_manager.cpp
+ renderer/upsampler/upsampler_manager.h
+ renderer/upsampler/upsampler_state.h
+ renderer/voice/voice_channel_resource.h
+ renderer/voice/voice_context.cpp
+ renderer/voice/voice_context.h
+ renderer/voice/voice_info.cpp
+ renderer/voice/voice_info.h
+ renderer/voice/voice_state.h
+ sink/cubeb_sink.cpp
+ sink/cubeb_sink.h
+ sink/null_sink.h
+ sink/sdl2_sink.cpp
+ sink/sdl2_sink.h
+ sink/sink.h
+ sink/sink_details.cpp
+ sink/sink_details.h
+ sink/sink_stream.cpp
+ sink/sink_stream.h
)
create_target_directory_groups(audio_core)
-if (NOT MSVC)
+if (MSVC)
+ target_compile_options(audio_core PRIVATE
+ /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
+ /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data
+ /we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch
+ /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
+ /we4456 # Declaration of 'identifier' hides previous local declaration
+ /we4457 # Declaration of 'identifier' hides function parameter
+ /we4458 # Declaration of 'identifier' hides class member
+ /we4459 # Declaration of 'identifier' hides global declaration
+ )
+else()
target_compile_options(audio_core PRIVATE
-Werror=conversion
-Werror=ignored-qualifiers
-Werror=shadow
- -Werror=unused-parameter
-Werror=unused-variable
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
@@ -63,7 +226,9 @@ if (NOT MSVC)
endif()
target_link_libraries(audio_core PUBLIC common core)
-target_link_libraries(audio_core PRIVATE SoundTouch)
+if (ARCHITECTURE_x86_64)
+ target_link_libraries(audio_core PRIVATE dynarmic)
+endif()
if(ENABLE_CUBEB)
target_link_libraries(audio_core PRIVATE cubeb)
diff --git a/src/audio_core/algorithm/filter.cpp b/src/audio_core/algorithm/filter.cpp
deleted file mode 100644
index 01b8dff6b..000000000
--- a/src/audio_core/algorithm/filter.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#define _USE_MATH_DEFINES
-
-#include <algorithm>
-#include <array>
-#include <cmath>
-#include <vector>
-#include "audio_core/algorithm/filter.h"
-#include "common/common_types.h"
-
-namespace AudioCore {
-
-Filter Filter::LowPass(double cutoff, double Q) {
- const double w0 = 2.0 * M_PI * cutoff;
- const double sin_w0 = std::sin(w0);
- const double cos_w0 = std::cos(w0);
- const double alpha = sin_w0 / (2 * Q);
-
- const double a0 = 1 + alpha;
- const double a1 = -2.0 * cos_w0;
- const double a2 = 1 - alpha;
- const double b0 = 0.5 * (1 - cos_w0);
- const double b1 = 1.0 * (1 - cos_w0);
- const double b2 = 0.5 * (1 - cos_w0);
-
- return {a0, a1, a2, b0, b1, b2};
-}
-
-Filter::Filter() : Filter(1.0, 0.0, 0.0, 1.0, 0.0, 0.0) {}
-
-Filter::Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_)
- : a1(a1_ / a0_), a2(a2_ / a0_), b0(b0_ / a0_), b1(b1_ / a0_), b2(b2_ / a0_) {}
-
-void Filter::Process(std::vector<s16>& signal) {
- const std::size_t num_frames = signal.size() / 2;
- for (std::size_t i = 0; i < num_frames; i++) {
- std::rotate(in.begin(), in.end() - 1, in.end());
- std::rotate(out.begin(), out.end() - 1, out.end());
-
- for (std::size_t ch = 0; ch < channel_count; ch++) {
- in[0][ch] = signal[i * channel_count + ch];
-
- out[0][ch] = b0 * in[0][ch] + b1 * in[1][ch] + b2 * in[2][ch] - a1 * out[1][ch] -
- a2 * out[2][ch];
-
- signal[i * 2 + ch] = static_cast<s16>(std::clamp(out[0][ch], -32768.0, 32767.0));
- }
- }
-}
-
-/// Calculates the appropriate Q for each biquad in a cascading filter.
-/// @param total_count The total number of biquads to be cascaded.
-/// @param index 0-index of the biquad to calculate the Q value for.
-static double CascadingBiquadQ(std::size_t total_count, std::size_t index) {
- const auto pole =
- M_PI * static_cast<double>(2 * index + 1) / (4.0 * static_cast<double>(total_count));
- return 1.0 / (2.0 * std::cos(pole));
-}
-
-CascadingFilter CascadingFilter::LowPass(double cutoff, std::size_t cascade_size) {
- std::vector<Filter> cascade(cascade_size);
- for (std::size_t i = 0; i < cascade_size; i++) {
- cascade[i] = Filter::LowPass(cutoff, CascadingBiquadQ(cascade_size, i));
- }
- return CascadingFilter{std::move(cascade)};
-}
-
-CascadingFilter::CascadingFilter() = default;
-CascadingFilter::CascadingFilter(std::vector<Filter> filters_) : filters(std::move(filters_)) {}
-
-void CascadingFilter::Process(std::vector<s16>& signal) {
- for (auto& filter : filters) {
- filter.Process(signal);
- }
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/algorithm/filter.h b/src/audio_core/algorithm/filter.h
deleted file mode 100644
index a291fe79b..000000000
--- a/src/audio_core/algorithm/filter.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <vector>
-#include "common/common_types.h"
-
-namespace AudioCore {
-
-/// Digital biquad filter:
-///
-/// b0 + b1 z^-1 + b2 z^-2
-/// H(z) = ------------------------
-/// a0 + a1 z^-1 + b2 z^-2
-class Filter {
-public:
- /// Creates a low-pass filter.
- /// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0.
- /// @param Q Determines the quality factor of this filter.
- static Filter LowPass(double cutoff, double Q = 0.7071);
-
- /// Passthrough filter.
- Filter();
-
- Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_);
-
- void Process(std::vector<s16>& signal);
-
-private:
- static constexpr std::size_t channel_count = 2;
-
- /// Coefficients are in normalized form (a0 = 1.0).
- double a1, a2, b0, b1, b2;
- /// Input History
- std::array<std::array<double, channel_count>, 3> in;
- /// Output History
- std::array<std::array<double, channel_count>, 3> out;
-};
-
-/// Cascade filters to build up higher-order filters from lower-order ones.
-class CascadingFilter {
-public:
- /// Creates a cascading low-pass filter.
- /// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0.
- /// @param cascade_size Number of biquads in cascade.
- static CascadingFilter LowPass(double cutoff, std::size_t cascade_size);
-
- /// Passthrough.
- CascadingFilter();
-
- explicit CascadingFilter(std::vector<Filter> filters_);
-
- void Process(std::vector<s16>& signal);
-
-private:
- std::vector<Filter> filters;
-};
-
-} // namespace AudioCore
diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp
deleted file mode 100644
index 3b4144e21..000000000
--- a/src/audio_core/algorithm/interpolate.cpp
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#define _USE_MATH_DEFINES
-
-#include <algorithm>
-#include <climits>
-#include <cmath>
-#include <vector>
-
-#include "audio_core/algorithm/interpolate.h"
-#include "common/common_types.h"
-#include "common/logging/log.h"
-
-namespace AudioCore {
-
-constexpr std::array<s16, 512> curve_lut0{
- 6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239,
- 19412, 7093, 22, 6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377,
- 7472, 41, 5773, 19361, 7600, 48, 5659, 19342, 7728, 55, 5546, 19321, 7857,
- 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77, 5213, 19245, 8249, 84,
- 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109, 4785,
- 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988,
- 9183, 147, 4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590,
- 179, 4083, 18793, 9726, 190, 3987, 18738, 9863, 202, 3893, 18682, 10000, 215,
- 3800, 18624, 10137, 228, 3709, 18563, 10274, 241, 3618, 18500, 10411, 255, 3529,
- 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300, 3269, 18230,
- 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375,
- 369, 2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428,
- 2709, 17681, 11925, 449, 2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489,
- 17418, 12334, 517, 2418, 17327, 12470, 541, 2348, 17234, 12606, 566, 2280, 17140,
- 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647, 2083, 16846, 13144,
- 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766,
- 1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667,
- 16109, 14062, 901, 1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772,
- 14445, 1013, 1457, 15657, 14571, 1052, 1407, 15540, 14695, 1093, 1359, 15423, 14819,
- 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221, 1221, 15064, 15185, 1266,
- 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407, 1052,
- 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191,
- 15998, 1613, 901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327,
- 1780, 798, 13673, 16434, 1838, 766, 13541, 16539, 1897, 735, 13409, 16643, 1958,
- 704, 13277, 16745, 2020, 675, 13144, 16846, 2083, 647, 13010, 16946, 2147, 619,
- 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348, 541, 12470,
- 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595,
- 2635, 449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863,
- 388, 11513, 17927, 2942, 369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334,
- 11100, 18157, 3186, 317, 10962, 18230, 3269, 300, 10824, 18300, 3355, 285, 10687,
- 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618, 241, 10274, 18563,
- 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987,
- 190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157,
- 9318, 18942, 4377, 147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914,
- 19073, 4681, 118, 8780, 19112, 4785, 109, 8646, 19148, 4890, 101, 8513, 19183,
- 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213, 77, 8118, 19273, 5323,
- 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659, 48,
- 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219,
- 19403, 6121, 22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424,
- 6479, 3, 6722, 19426, 6600};
-
-constexpr std::array<s16, 512> curve_lut1{
- -68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450,
- 32586, 512, -36, -568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454,
- 1000, -69, -891, 32393, 1174, -80, -990, 32323, 1352, -92, -1084, 32244, 1536,
- -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128, -1338, 31956, 2118, -140,
- -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180, -1617,
- 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995,
- 3657, -240, -1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393,
- -289, -1951, 30272, 4648, -307, -1984, 30072, 4908, -325, -2014, 29866, 5172, -343,
- -2040, 29652, 5442, -362, -2063, 29431, 5716, -382, -2083, 29203, 5994, -403, -2100,
- 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468, -2133, 28226,
- 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066,
- -563, -2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641,
- -2121, 26285, 9336, -668, -2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084,
- 25375, 10326, -753, -2067, 25063, 10662, -783, -2049, 24746, 11000, -813, -2030, 24425,
- 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907, -1962, 23438, 12382,
- -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039,
- -1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764,
- 21027, 14877, -1176, -1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959,
- 15965, -1282, -1633, 19600, 16329, -1317, -1599, 19239, 16694, -1353, -1564, 18878, 17058,
- -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459, -1459, 17787, 18151, -1495,
- -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599, -1317,
- 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239,
- 20673, -1732, -1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729,
- -1825, -1072, 13798, 22077, -1855, -1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911,
- -972, 12733, 23103, -1937, -939, 12382, 23438, -1962, -907, 12033, 23771, -1986, -875,
- 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049, -783, 10662,
- 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987,
- -2111, -668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136,
- -588, 8378, 27151, -2141, -563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514,
- 7453, 27966, -2139, -490, 7153, 28226, -2133, -468, 6857, 28480, -2125, -445, 6565,
- 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083, -382, 5716, 29431,
- -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984,
- -307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256,
- 3897, 30826, -1830, -240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192,
- 31310, -1676, -194, 2967, 31456, -1617, -180, 2747, 31593, -1554, -167, 2532, 31723,
- -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338, -128, 1919, 32061, -1258,
- -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990, -80,
- 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669,
- 32551, -568, -36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630,
- -200, -5, 69, 32639, -68};
-
-constexpr std::array<s16, 512> curve_lut2{
- 3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811,
- 26253, 3751, -42, 2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169,
- 4199, -54, 2338, 26130, 4354, -58, 2227, 26085, 4512, -63, 2120, 26035, 4673,
- -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76, 1813, 25852, 5174, -81,
- 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98, 1442,
- 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239,
- 6442, -121, 1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027,
- -140, 897, 24776, 7227, -146, 829, 24648, 7430, -153, 764, 24516, 7635, -159,
- 701, 24379, 7842, -166, 641, 24237, 8052, -174, 583, 24091, 8264, -181, 526,
- 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202, 371, 23462,
- 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809,
- -230, 194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250,
- 81, 22208, 10735, -258, 47, 22015, 10970, -265, 15, 21818, 11206, -271, -16,
- 21618, 11444, -277, -44, 21415, 11684, -283, -71, 21208, 11924, -290, -97, 20999,
- 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306, -163, 20354, 12898,
- -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325,
- -234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273,
- 18765, 14625, -337, -284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057,
- 15369, -341, -310, 17817, 15617, -341, -317, 17577, 15864, -340, -323, 17335, 16111,
- -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336, -336, 16603, 16848, -332,
- -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317, -341,
- 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873,
- 18531, -284, -337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230,
- -248, -328, 13882, 19459, -234, -325, 13635, 19686, -218, -321, 13389, 19911, -201,
- -316, 13143, 20134, -183, -311, 12898, 20354, -163, -306, 12653, 20571, -143, -302,
- 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71, -283, 11684,
- 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015,
- 47, -258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154,
- -237, 10038, 22769, 194, -230, 9809, 22948, 236, -222, 9583, 23123, 279, -215,
- 9358, 23295, 324, -209, 9135, 23462, 371, -202, 8914, 23626, 420, -194, 8695,
- 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583, -174, 8052, 24237,
- 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829,
- -146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127,
- 6635, 25131, 1115, -121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066,
- 25440, 1357, -103, 5882, 25533, 1442, -98, 5701, 25621, 1531, -92, 5522, 25704,
- 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813, -76, 5004, 25919, 1912,
- -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227, -58,
- 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897,
- 26230, 2688, -42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281,
- 3064, -32, 3329, 26287, 3195};
-
-std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio) {
- if (input.size() < 2)
- return {};
-
- if (ratio <= 0) {
- LOG_ERROR(Audio, "Nonsensical interpolation ratio {}", ratio);
- return input;
- }
-
- const s32 step{static_cast<s32>(ratio * 0x8000)};
- const std::array<s16, 512>& lut = [step] {
- if (step > 0xaaaa) {
- return curve_lut0;
- }
- if (step <= 0x8000) {
- return curve_lut1;
- }
- return curve_lut2;
- }();
-
- const std::size_t num_frames{input.size() / 2};
-
- std::vector<s16> output;
- output.reserve(static_cast<std::size_t>(static_cast<double>(input.size()) / ratio +
- InterpolationState::taps));
-
- for (std::size_t frame{}; frame < num_frames; ++frame) {
- const std::size_t lut_index{(state.fraction >> 8) * InterpolationState::taps};
-
- std::rotate(state.history.begin(), state.history.end() - 1, state.history.end());
- state.history[0][0] = input[frame * 2 + 0];
- state.history[0][1] = input[frame * 2 + 1];
-
- while (state.position <= 1.0) {
- const s32 left{state.history[0][0] * lut[lut_index + 0] +
- state.history[1][0] * lut[lut_index + 1] +
- state.history[2][0] * lut[lut_index + 2] +
- state.history[3][0] * lut[lut_index + 3]};
- const s32 right{state.history[0][1] * lut[lut_index + 0] +
- state.history[1][1] * lut[lut_index + 1] +
- state.history[2][1] * lut[lut_index + 2] +
- state.history[3][1] * lut[lut_index + 3]};
- const s32 new_offset{state.fraction + step};
-
- state.fraction = new_offset & 0x7fff;
-
- output.emplace_back(static_cast<s16>(std::clamp(left >> 15, SHRT_MIN, SHRT_MAX)));
- output.emplace_back(static_cast<s16>(std::clamp(right >> 15, SHRT_MIN, SHRT_MAX)));
-
- state.position += ratio;
- }
- state.position -= 1.0;
- }
-
- return output;
-}
-
-void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) {
- const std::array<s16, 512>& lut = [pitch] {
- if (pitch > 0xaaaa) {
- return curve_lut0;
- }
- if (pitch <= 0x8000) {
- return curve_lut1;
- }
- return curve_lut2;
- }();
-
- std::size_t index{};
-
- for (std::size_t i = 0; i < sample_count; i++) {
- const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4};
- const auto l0 = lut[lut_index + 0];
- const auto l1 = lut[lut_index + 1];
- const auto l2 = lut[lut_index + 2];
- const auto l3 = lut[lut_index + 3];
-
- const auto s0 = static_cast<s32>(input[index + 0]);
- const auto s1 = static_cast<s32>(input[index + 1]);
- const auto s2 = static_cast<s32>(input[index + 2]);
- const auto s3 = static_cast<s32>(input[index + 3]);
-
- output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15;
- fraction += pitch;
- index += (fraction >> 15);
- fraction &= 0x7fff;
- }
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/algorithm/interpolate.h b/src/audio_core/algorithm/interpolate.h
deleted file mode 100644
index d534077af..000000000
--- a/src/audio_core/algorithm/interpolate.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <vector>
-
-#include "common/common_types.h"
-
-namespace AudioCore {
-
-struct InterpolationState {
- static constexpr std::size_t taps{4};
- static constexpr std::size_t history_size{taps * 2 - 1};
- std::array<std::array<s16, 2>, history_size> history{};
- double position{};
- s32 fraction{};
-};
-
-/// Interpolates input signal to produce output signal.
-/// @param input The signal to interpolate.
-/// @param ratio Interpolation ratio.
-/// ratio > 1.0 results in fewer output samples.
-/// ratio < 1.0 results in more output samples.
-/// @returns Output signal.
-std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio);
-
-/// Interpolates input signal to produce output signal.
-/// @param input The signal to interpolate.
-/// @param input_rate The sample rate of input.
-/// @param output_rate The desired sample rate of the output.
-/// @returns Output signal.
-inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
- u32 input_rate, u32 output_rate) {
- const double ratio = static_cast<double>(input_rate) / static_cast<double>(output_rate);
- return Interpolate(state, std::move(input), ratio);
-}
-
-/// Nintendo Switchs DSP resampling algorithm. Based on a single channel
-void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count);
-
-} // namespace AudioCore
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
new file mode 100644
index 000000000..07a679c32
--- /dev/null
+++ b/src/audio_core/audio_core.cpp
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/audio_core.h"
+#include "audio_core/sink/sink_details.h"
+#include "common/settings.h"
+#include "core/core.h"
+
+namespace AudioCore {
+
+AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>()} {
+ CreateSinks();
+ // Must be created after the sinks
+ adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink);
+}
+
+AudioCore ::~AudioCore() {
+ Shutdown();
+}
+
+void AudioCore::CreateSinks() {
+ const auto& sink_id{Settings::values.sink_id};
+ const auto& audio_output_device_id{Settings::values.audio_output_device_id};
+ const auto& audio_input_device_id{Settings::values.audio_input_device_id};
+
+ output_sink = Sink::CreateSinkFromID(sink_id.GetValue(), audio_output_device_id.GetValue());
+ input_sink = Sink::CreateSinkFromID(sink_id.GetValue(), audio_input_device_id.GetValue());
+}
+
+void AudioCore::Shutdown() {
+ audio_manager->Shutdown();
+}
+
+AudioManager& AudioCore::GetAudioManager() {
+ return *audio_manager;
+}
+
+Sink::Sink& AudioCore::GetOutputSink() {
+ return *output_sink;
+}
+
+Sink::Sink& AudioCore::GetInputSink() {
+ return *input_sink;
+}
+
+AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
+ return *adsp;
+}
+
+void AudioCore::SetNVDECActive(bool active) {
+ nvdec_active = active;
+}
+
+bool AudioCore::IsNVDECActive() const {
+ return nvdec_active;
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h
new file mode 100644
index 000000000..e33e00a3e
--- /dev/null
+++ b/src/audio_core/audio_core.h
@@ -0,0 +1,90 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+
+#include "audio_core/audio_manager.h"
+#include "audio_core/renderer/adsp/adsp.h"
+#include "audio_core/sink/sink.h"
+
+namespace Core {
+class System;
+}
+
+namespace AudioCore {
+
+class AudioManager;
+/**
+ * Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP.
+ */
+class AudioCore {
+public:
+ explicit AudioCore(Core::System& system);
+ ~AudioCore();
+
+ /**
+ * Shutdown the audio core.
+ */
+ void Shutdown();
+
+ /**
+ * Get a reference to the audio manager.
+ *
+ * @return Ref to the audio manager.
+ */
+ AudioManager& GetAudioManager();
+
+ /**
+ * Get the audio output sink currently in use.
+ *
+ * @return Ref to the sink.
+ */
+ Sink::Sink& GetOutputSink();
+
+ /**
+ * Get the audio input sink currently in use.
+ *
+ * @return Ref to the sink.
+ */
+ Sink::Sink& GetInputSink();
+
+ /**
+ * Get the ADSP.
+ *
+ * @return Ref to the ADSP.
+ */
+ AudioRenderer::ADSP::ADSP& GetADSP();
+
+ /**
+ * Toggle NVDEC state, used to avoid stall in playback.
+ *
+ * @param active - Set true if nvdec is active, otherwise false.
+ */
+ void SetNVDECActive(bool active);
+
+ /**
+ * Get NVDEC state.
+ */
+ bool IsNVDECActive() const;
+
+private:
+ /**
+ * Create the sinks on startup.
+ */
+ void CreateSinks();
+
+ /// Main audio manager for audio in/out
+ std::unique_ptr<AudioManager> audio_manager;
+ /// Sink used for audio renderer and audio out
+ std::unique_ptr<Sink::Sink> output_sink;
+ /// Sink used for audio input
+ std::unique_ptr<Sink::Sink> input_sink;
+ /// The ADSP in the sysmodule
+ std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
+ /// Is NVDec currently active?
+ bool nvdec_active{false};
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/audio_event.cpp b/src/audio_core/audio_event.cpp
new file mode 100644
index 000000000..424049c7a
--- /dev/null
+++ b/src/audio_core/audio_event.cpp
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/audio_event.h"
+#include "common/assert.h"
+
+namespace AudioCore {
+
+size_t Event::GetManagerIndex(const Type type) const {
+ switch (type) {
+ case Type::AudioInManager:
+ return 0;
+ case Type::AudioOutManager:
+ return 1;
+ case Type::FinalOutputRecorderManager:
+ return 2;
+ case Type::Max:
+ return 3;
+ default:
+ UNREACHABLE();
+ }
+ return 3;
+}
+
+void Event::SetAudioEvent(const Type type, const bool signalled) {
+ events_signalled[GetManagerIndex(type)] = signalled;
+ if (signalled) {
+ manager_event.notify_one();
+ }
+}
+
+bool Event::CheckAudioEventSet(const Type type) const {
+ return events_signalled[GetManagerIndex(type)];
+}
+
+std::mutex& Event::GetAudioEventLock() {
+ return event_lock;
+}
+
+std::condition_variable_any& Event::GetAudioEvent() {
+ return manager_event;
+}
+
+bool Event::Wait(std::unique_lock<std::mutex>& l, const std::chrono::seconds timeout) {
+ bool timed_out{false};
+ if (!manager_event.wait_for(l, timeout, [&]() {
+ return std::ranges::any_of(events_signalled, [](bool x) { return x; });
+ })) {
+ timed_out = true;
+ }
+ return timed_out;
+}
+
+void Event::ClearEvents() {
+ events_signalled[0] = false;
+ events_signalled[1] = false;
+ events_signalled[2] = false;
+ events_signalled[3] = false;
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/audio_event.h b/src/audio_core/audio_event.h
new file mode 100644
index 000000000..012d2ed70
--- /dev/null
+++ b/src/audio_core/audio_event.h
@@ -0,0 +1,92 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+
+namespace AudioCore {
+/**
+ * Responsible for the input/output events, set by the stream backend when buffers are consumed, and
+ * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer
+ * recycling going.
+ * In a real Switch this is not a separate class, and exists entirely within the audio manager.
+ * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can
+ * wait on multiple events at once, and the events are not needed by the backend.
+ */
+class Event {
+public:
+ enum class Type {
+ AudioInManager,
+ AudioOutManager,
+ FinalOutputRecorderManager,
+ Max,
+ };
+
+ /**
+ * Convert a manager type to an index.
+ *
+ * @param type - The manager type to convert
+ * @return The index of the type.
+ */
+ size_t GetManagerIndex(Type type) const;
+
+ /**
+ * Set an audio event to true or false.
+ *
+ * @param type - The manager type to signal.
+ * @param signalled - Its signal state.
+ */
+ void SetAudioEvent(Type type, bool signalled);
+
+ /**
+ * Check if the given manager type is signalled.
+ *
+ * @param type - The manager type to check.
+ * @return True if the event is signalled, otherwise false.
+ */
+ bool CheckAudioEventSet(Type type) const;
+
+ /**
+ * Get the lock for audio events.
+ *
+ * @return Reference to the lock.
+ */
+ std::mutex& GetAudioEventLock();
+
+ /**
+ * Get the manager event, this signals the audio manager to release buffers and signal the game
+ * for more.
+ *
+ * @return Reference to the condition variable.
+ */
+ std::condition_variable_any& GetAudioEvent();
+
+ /**
+ * Wait on the manager_event.
+ *
+ * @param l - Lock held by the wait.
+ * @param timeout - Timeout for the wait. This is 2 seconds by default.
+ * @return True if the wait timed out, otherwise false if signalled.
+ */
+ bool Wait(std::unique_lock<std::mutex>& l, std::chrono::seconds timeout);
+
+ /**
+ * Reset all manager events.
+ */
+ void ClearEvents();
+
+private:
+ /// Lock, used by the audio manager
+ std::mutex event_lock;
+ /// Array of events, one per system type (see Type), last event is used to terminate
+ std::array<std::atomic<bool>, 4> events_signalled;
+ /// Event to signal the audio manager
+ std::condition_variable_any manager_event;
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp
new file mode 100644
index 000000000..f39fb4002
--- /dev/null
+++ b/src/audio_core/audio_in_manager.cpp
@@ -0,0 +1,91 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/audio_core.h"
+#include "audio_core/audio_in_manager.h"
+#include "audio_core/audio_manager.h"
+#include "audio_core/in/audio_in.h"
+#include "audio_core/sink/sink_details.h"
+#include "common/settings.h"
+#include "core/core.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace AudioCore::AudioIn {
+
+Manager::Manager(Core::System& system_) : system{system_} {
+ std::iota(session_ids.begin(), session_ids.end(), 0);
+ num_free_sessions = MaxInSessions;
+}
+
+Result Manager::AcquireSessionId(size_t& session_id) {
+ if (num_free_sessions == 0) {
+ LOG_ERROR(Service_Audio, "All 4 AudioIn sessions are in use, cannot create any more");
+ return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED;
+ }
+ session_id = session_ids[next_session_id];
+ next_session_id = (next_session_id + 1) % MaxInSessions;
+ num_free_sessions--;
+ return ResultSuccess;
+}
+
+void Manager::ReleaseSessionId(const size_t session_id) {
+ std::scoped_lock l{mutex};
+ LOG_DEBUG(Service_Audio, "Freeing AudioIn session {}", session_id);
+ session_ids[free_session_id] = session_id;
+ num_free_sessions++;
+ free_session_id = (free_session_id + 1) % MaxInSessions;
+ sessions[session_id].reset();
+ applet_resource_user_ids[session_id] = 0;
+}
+
+Result Manager::LinkToManager() {
+ std::scoped_lock l{mutex};
+ if (!linked_to_manager) {
+ AudioManager& manager{system.AudioCore().GetAudioManager()};
+ manager.SetInManager(std::bind(&Manager::BufferReleaseAndRegister, this));
+ linked_to_manager = true;
+ }
+
+ return ResultSuccess;
+}
+
+void Manager::Start() {
+ if (sessions_started) {
+ return;
+ }
+
+ std::scoped_lock l{mutex};
+ for (auto& session : sessions) {
+ if (session) {
+ session->StartSession();
+ }
+ }
+
+ sessions_started = true;
+}
+
+void Manager::BufferReleaseAndRegister() {
+ std::scoped_lock l{mutex};
+ for (auto& session : sessions) {
+ if (session != nullptr) {
+ session->ReleaseAndRegisterBuffers();
+ }
+ }
+}
+
+u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names,
+ [[maybe_unused]] const u32 max_count,
+ [[maybe_unused]] const bool filter) {
+ std::scoped_lock l{mutex};
+
+ LinkToManager();
+
+ auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)};
+ if (input_devices.size() > 1) {
+ names.emplace_back("Uac");
+ return 1;
+ }
+ return 0;
+}
+
+} // namespace AudioCore::AudioIn
diff --git a/src/audio_core/audio_in_manager.h b/src/audio_core/audio_in_manager.h
new file mode 100644
index 000000000..8a519df99
--- /dev/null
+++ b/src/audio_core/audio_in_manager.h
@@ -0,0 +1,93 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <mutex>
+#include <vector>
+
+#include "audio_core/renderer/audio_device.h"
+
+namespace Core {
+class System;
+}
+
+namespace AudioCore::AudioIn {
+class In;
+
+constexpr size_t MaxInSessions = 4;
+/**
+ * Manages all audio in sessions.
+ */
+class Manager {
+public:
+ explicit Manager(Core::System& system);
+
+ /**
+ * Acquire a free session id for opening a new audio in.
+ *
+ * @param session_id - Output session_id.
+ * @return Result code.
+ */
+ Result AcquireSessionId(size_t& session_id);
+
+ /**
+ * Release a session id on close.
+ *
+ * @param session_id - Session id to free.
+ */
+ void ReleaseSessionId(size_t session_id);
+
+ /**
+ * Link the audio in manager to the main audio manager.
+ *
+ * @return Result code.
+ */
+ Result LinkToManager();
+
+ /**
+ * Start the audio in manager.
+ */
+ void Start();
+
+ /**
+ * Callback function, called by the audio manager when the audio in event is signalled.
+ */
+ void BufferReleaseAndRegister();
+
+ /**
+ * Get a list of audio in device names.
+ *
+ * @param names - Output container to write names to.
+ * @param max_count - Maximum number of device names to write. Unused
+ * @param filter - Should the list be filtered? Unused.
+ *
+ * @return Number of names written.
+ */
+ u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names,
+ u32 max_count, bool filter);
+
+ /// Core system
+ Core::System& system;
+ /// Array of session ids
+ std::array<size_t, MaxInSessions> session_ids{};
+ /// Array of resource user ids
+ std::array<size_t, MaxInSessions> applet_resource_user_ids{};
+ /// Pointer to each open session
+ std::array<std::shared_ptr<In>, MaxInSessions> sessions{};
+ /// The number of free sessions
+ size_t num_free_sessions{};
+ /// The next session id to be taken
+ size_t next_session_id{};
+ /// The next session id to be freed
+ size_t free_session_id{};
+ /// Whether this is linked to the audio manager
+ bool linked_to_manager{};
+ /// Whether the sessions have been started
+ bool sessions_started{};
+ /// Protect state due to audio manager callback
+ std::recursive_mutex mutex{};
+};
+
+} // namespace AudioCore::AudioIn
diff --git a/src/audio_core/audio_manager.cpp b/src/audio_core/audio_manager.cpp
new file mode 100644
index 000000000..2acde668e
--- /dev/null
+++ b/src/audio_core/audio_manager.cpp
@@ -0,0 +1,81 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/audio_manager.h"
+#include "core/core.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace AudioCore {
+
+AudioManager::AudioManager() {
+ thread = std::jthread([this]() { ThreadFunc(); });
+}
+
+void AudioManager::Shutdown() {
+ running = false;
+ events.SetAudioEvent(Event::Type::Max, true);
+ thread.join();
+}
+
+Result AudioManager::SetOutManager(BufferEventFunc buffer_func) {
+ if (!running) {
+ return Service::Audio::ERR_OPERATION_FAILED;
+ }
+
+ std::scoped_lock l{lock};
+
+ const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)};
+ if (buffer_events[index] == nullptr) {
+ buffer_events[index] = std::move(buffer_func);
+ needs_update = true;
+ events.SetAudioEvent(Event::Type::AudioOutManager, true);
+ }
+ return ResultSuccess;
+}
+
+Result AudioManager::SetInManager(BufferEventFunc buffer_func) {
+ if (!running) {
+ return Service::Audio::ERR_OPERATION_FAILED;
+ }
+
+ std::scoped_lock l{lock};
+
+ const auto index{events.GetManagerIndex(Event::Type::AudioInManager)};
+ if (buffer_events[index] == nullptr) {
+ buffer_events[index] = std::move(buffer_func);
+ needs_update = true;
+ events.SetAudioEvent(Event::Type::AudioInManager, true);
+ }
+ return ResultSuccess;
+}
+
+void AudioManager::SetEvent(const Event::Type type, const bool signalled) {
+ events.SetAudioEvent(type, signalled);
+}
+
+void AudioManager::ThreadFunc() {
+ std::unique_lock l{events.GetAudioEventLock()};
+ events.ClearEvents();
+ running = true;
+
+ while (running) {
+ const auto timed_out{events.Wait(l, std::chrono::seconds(2))};
+
+ if (events.CheckAudioEventSet(Event::Type::Max)) {
+ break;
+ }
+
+ for (size_t i = 0; i < buffer_events.size(); i++) {
+ const auto event_type = static_cast<Event::Type>(i);
+
+ if (events.CheckAudioEventSet(event_type) || timed_out) {
+ if (buffer_events[i]) {
+ buffer_events[i]();
+ }
+ }
+ events.SetAudioEvent(event_type, false);
+ }
+ }
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/audio_manager.h b/src/audio_core/audio_manager.h
new file mode 100644
index 000000000..abf077de4
--- /dev/null
+++ b/src/audio_core/audio_manager.h
@@ -0,0 +1,86 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <functional>
+#include <mutex>
+#include <thread>
+
+#include "audio_core/audio_event.h"
+
+union Result;
+
+namespace AudioCore {
+
+/**
+ * The AudioManager's main purpose is to wait for buffer events for the audio in and out managers,
+ * and call an associated callback to release buffers.
+ *
+ * Execution pattern is:
+ * Buffers appended ->
+ * Buffers queued and played by the backend stream ->
+ * When consumed, set the corresponding manager event and signal the audio manager ->
+ * Consumed buffers are released, game is signalled ->
+ * Game appends more buffers.
+ *
+ * This is only used by audio in and audio out.
+ */
+class AudioManager {
+ using BufferEventFunc = std::function<void()>;
+
+public:
+ explicit AudioManager();
+
+ /**
+ * Shutdown the audio manager.
+ */
+ void Shutdown();
+
+ /**
+ * Register the out manager, keeping a function to be called when the out event is signalled.
+ *
+ * @param buffer_func - Function to be called on signal.
+ * @return Result code.
+ */
+ Result SetOutManager(BufferEventFunc buffer_func);
+
+ /**
+ * Register the in manager, keeping a function to be called when the in event is signalled.
+ *
+ * @param buffer_func - Function to be called on signal.
+ * @return Result code.
+ */
+ Result SetInManager(BufferEventFunc buffer_func);
+
+ /**
+ * Set an event to signalled, and signal the thread.
+ *
+ * @param type - Manager type to set.
+ * @param signalled - Set the event to true or false?
+ */
+ void SetEvent(Event::Type type, bool signalled);
+
+private:
+ /**
+ * Main thread, waiting on a manager signal and calling the registered function.
+ */
+ void ThreadFunc();
+
+ /// Is the main thread running?
+ std::atomic<bool> running{};
+ /// Unused
+ bool needs_update{};
+ /// Events to be set and signalled
+ Event events{};
+ /// Callbacks for each manager
+ std::array<BufferEventFunc, 3> buffer_events{};
+ /// General lock
+ std::mutex lock{};
+ /// Main thread for waiting and callbacks
+ std::jthread thread;
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp
deleted file mode 100644
index 44a899d08..000000000
--- a/src/audio_core/audio_out.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "audio_core/audio_out.h"
-#include "audio_core/sink.h"
-#include "audio_core/sink_details.h"
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "common/settings.h"
-
-namespace AudioCore {
-
-/// Returns the stream format from the specified number of channels
-static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
- switch (num_channels) {
- case 1:
- return Stream::Format::Mono16;
- case 2:
- return Stream::Format::Stereo16;
- case 6:
- return Stream::Format::Multi51Channel16;
- }
-
- UNIMPLEMENTED_MSG("Unimplemented num_channels={}", num_channels);
- return {};
-}
-
-StreamPtr AudioOut::OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate,
- u32 num_channels, std::string&& name,
- Stream::ReleaseCallback&& release_callback) {
- if (!sink) {
- sink = CreateSinkFromID(Settings::values.sink_id.GetValue(),
- Settings::values.audio_device_id.GetValue());
- }
-
- return std::make_shared<Stream>(
- core_timing, sample_rate, ChannelsToStreamFormat(num_channels), std::move(release_callback),
- sink->AcquireSinkStream(sample_rate, num_channels, name), std::move(name));
-}
-
-std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream,
- std::size_t max_count) {
- return stream->GetTagsAndReleaseBuffers(max_count);
-}
-
-std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream) {
- return stream->GetTagsAndReleaseBuffers();
-}
-
-void AudioOut::StartStream(StreamPtr stream) {
- stream->Play();
-}
-
-void AudioOut::StopStream(StreamPtr stream) {
- stream->Stop();
-}
-
-bool AudioOut::QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data) {
- return stream->QueueBuffer(std::make_shared<Buffer>(tag, std::move(data)));
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/audio_out.h b/src/audio_core/audio_out.h
deleted file mode 100644
index 6ce08cd0d..000000000
--- a/src/audio_core/audio_out.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "audio_core/buffer.h"
-#include "audio_core/sink.h"
-#include "audio_core/stream.h"
-#include "common/common_types.h"
-
-namespace Core::Timing {
-class CoreTiming;
-}
-
-namespace AudioCore {
-
-/**
- * Represents an audio playback interface, used to open and play audio streams
- */
-class AudioOut {
-public:
- /// Opens a new audio stream
- StreamPtr OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, u32 num_channels,
- std::string&& name, Stream::ReleaseCallback&& release_callback);
-
- /// Returns a vector of recently released buffers specified by tag for the specified stream
- std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream, std::size_t max_count);
-
- /// Returns a vector of all recently released buffers specified by tag for the specified stream
- std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream);
-
- /// Starts an audio stream for playback
- void StartStream(StreamPtr stream);
-
- /// Stops an audio stream that is currently playing
- void StopStream(StreamPtr stream);
-
- /// Queues a buffer into the specified audio stream, returns true on success
- bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data);
-
-private:
- SinkPtr sink;
-};
-
-} // namespace AudioCore
diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp
new file mode 100644
index 000000000..1766efde1
--- /dev/null
+++ b/src/audio_core/audio_out_manager.cpp
@@ -0,0 +1,81 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/audio_core.h"
+#include "audio_core/audio_manager.h"
+#include "audio_core/audio_out_manager.h"
+#include "audio_core/out/audio_out.h"
+#include "core/core.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace AudioCore::AudioOut {
+
+Manager::Manager(Core::System& system_) : system{system_} {
+ std::iota(session_ids.begin(), session_ids.end(), 0);
+ num_free_sessions = MaxOutSessions;
+}
+
+Result Manager::AcquireSessionId(size_t& session_id) {
+ if (num_free_sessions == 0) {
+ LOG_ERROR(Service_Audio, "All 12 Audio Out sessions are in use, cannot create any more");
+ return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED;
+ }
+ session_id = session_ids[next_session_id];
+ next_session_id = (next_session_id + 1) % MaxOutSessions;
+ num_free_sessions--;
+ return ResultSuccess;
+}
+
+void Manager::ReleaseSessionId(const size_t session_id) {
+ std::scoped_lock l{mutex};
+ LOG_DEBUG(Service_Audio, "Freeing AudioOut session {}", session_id);
+ session_ids[free_session_id] = session_id;
+ num_free_sessions++;
+ free_session_id = (free_session_id + 1) % MaxOutSessions;
+ sessions[session_id].reset();
+ applet_resource_user_ids[session_id] = 0;
+}
+
+Result Manager::LinkToManager() {
+ std::scoped_lock l{mutex};
+ if (!linked_to_manager) {
+ AudioManager& manager{system.AudioCore().GetAudioManager()};
+ manager.SetOutManager(std::bind(&Manager::BufferReleaseAndRegister, this));
+ linked_to_manager = true;
+ }
+
+ return ResultSuccess;
+}
+
+void Manager::Start() {
+ if (sessions_started) {
+ return;
+ }
+
+ std::scoped_lock l{mutex};
+ for (auto& session : sessions) {
+ if (session) {
+ session->StartSession();
+ }
+ }
+
+ sessions_started = true;
+}
+
+void Manager::BufferReleaseAndRegister() {
+ std::scoped_lock l{mutex};
+ for (auto& session : sessions) {
+ if (session != nullptr) {
+ session->ReleaseAndRegisterBuffers();
+ }
+ }
+}
+
+u32 Manager::GetAudioOutDeviceNames(
+ std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const {
+ names.emplace_back("DeviceOut");
+ return 1;
+}
+
+} // namespace AudioCore::AudioOut
diff --git a/src/audio_core/audio_out_manager.h b/src/audio_core/audio_out_manager.h
new file mode 100644
index 000000000..24981e08f
--- /dev/null
+++ b/src/audio_core/audio_out_manager.h
@@ -0,0 +1,89 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <mutex>
+
+#include "audio_core/renderer/audio_device.h"
+
+namespace Core {
+class System;
+}
+
+namespace AudioCore::AudioOut {
+class Out;
+
+constexpr size_t MaxOutSessions = 12;
+/**
+ * Manages all audio out sessions.
+ */
+class Manager {
+public:
+ explicit Manager(Core::System& system);
+
+ /**
+ * Acquire a free session id for opening a new audio out.
+ *
+ * @param session_id - Output session_id.
+ * @return Result code.
+ */
+ Result AcquireSessionId(size_t& session_id);
+
+ /**
+ * Release a session id on close.
+ *
+ * @param session_id - Session id to free.
+ */
+ void ReleaseSessionId(size_t session_id);
+
+ /**
+ * Link this manager to the main audio manager.
+ *
+ * @return Result code.
+ */
+ Result LinkToManager();
+
+ /**
+ * Start the audio out manager.
+ */
+ void Start();
+
+ /**
+ * Callback function, called by the audio manager when the audio out event is signalled.
+ */
+ void BufferReleaseAndRegister();
+
+ /**
+ * Get a list of audio out device names.
+ *
+ * @oaram names - Output container to write names to.
+ * @return Number of names written.
+ */
+ u32 GetAudioOutDeviceNames(
+ std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const;
+
+ /// Core system
+ Core::System& system;
+ /// Array of session ids
+ std::array<size_t, MaxOutSessions> session_ids{};
+ /// Array of resource user ids
+ std::array<size_t, MaxOutSessions> applet_resource_user_ids{};
+ /// Pointer to each open session
+ std::array<std::shared_ptr<Out>, MaxOutSessions> sessions{};
+ /// The number of free sessions
+ size_t num_free_sessions{};
+ /// The next session id to be taken
+ size_t next_session_id{};
+ /// The next session id to be freed
+ size_t free_session_id{};
+ /// Whether this is linked to the audio manager
+ bool linked_to_manager{};
+ /// Whether the sessions have been started
+ bool sessions_started{};
+ /// Protect state due to audio manager callback
+ std::recursive_mutex mutex{};
+};
+
+} // namespace AudioCore::AudioOut
diff --git a/src/audio_core/audio_render_manager.cpp b/src/audio_core/audio_render_manager.cpp
new file mode 100644
index 000000000..7aba2b423
--- /dev/null
+++ b/src/audio_core/audio_render_manager.cpp
@@ -0,0 +1,70 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/audio_render_manager.h"
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/common/feature_support.h"
+#include "core/core.h"
+
+namespace AudioCore::AudioRenderer {
+
+Manager::Manager(Core::System& system_)
+ : system{system_}, system_manager{std::make_unique<SystemManager>(system)} {
+ std::iota(session_ids.begin(), session_ids.end(), 0);
+}
+
+Manager::~Manager() {
+ Stop();
+}
+
+void Manager::Stop() {
+ system_manager->Stop();
+}
+
+SystemManager& Manager::GetSystemManager() {
+ return *system_manager;
+}
+
+Result Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params,
+ u64& out_count) const {
+ if (!CheckValidRevision(params.revision)) {
+ return Service::Audio::ERR_INVALID_REVISION;
+ }
+
+ out_count = System::GetWorkBufferSize(params);
+
+ return ResultSuccess;
+}
+
+s32 Manager::GetSessionId() {
+ std::scoped_lock l{session_lock};
+ auto session_id{session_ids[session_count]};
+
+ if (session_id == -1) {
+ return -1;
+ }
+
+ session_ids[session_count] = -1;
+ session_count++;
+ return session_id;
+}
+
+void Manager::ReleaseSessionId(const s32 session_id) {
+ std::scoped_lock l{session_lock};
+ session_ids[--session_count] = session_id;
+}
+
+u32 Manager::GetSessionCount() const {
+ std::scoped_lock l{session_lock};
+ return session_count;
+}
+
+bool Manager::AddSystem(System& system_) {
+ return system_manager->Add(system_);
+}
+
+bool Manager::RemoveSystem(System& system_) {
+ return system_manager->Remove(system_);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h
new file mode 100644
index 000000000..bf4837190
--- /dev/null
+++ b/src/audio_core/audio_render_manager.h
@@ -0,0 +1,103 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <mutex>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/system_manager.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace Core {
+class System;
+}
+
+namespace AudioCore {
+struct AudioRendererParameterInternal;
+
+namespace AudioRenderer {
+/**
+ * Wrapper for the audio system manager, handles service calls.
+ */
+class Manager {
+public:
+ explicit Manager(Core::System& system);
+ ~Manager();
+
+ /**
+ * Stop the manager.
+ */
+ void Stop();
+
+ /**
+ * Get the system manager.
+ *
+ * @return The system manager.
+ */
+ SystemManager& GetSystemManager();
+
+ /**
+ * Get required size for the audio renderer workbuffer.
+ *
+ * @param params - Input parameters with the numbers of voices/mixes/sinks etc.
+ * @param out_count - Output size of the required workbuffer.
+ * @return Result code.
+ */
+ Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) const;
+
+ /**
+ * Get a new session id.
+ *
+ * @return The new session id. -1 if invalid, otherwise 0-MaxRendererSessions.
+ */
+ s32 GetSessionId();
+
+ /**
+ * Get the number of currently active sessions.
+ *
+ * @return The number of active sessions.
+ */
+ u32 GetSessionCount() const;
+
+ /**
+ * Add a renderer system to the manager.
+ * The system will be regularly called to generate commands for the AudioRenderer.
+ *
+ * @param system - The system to add.
+ * @return True if the system was successfully added, otherwise false.
+ */
+ bool AddSystem(System& system);
+
+ /**
+ * Remove a renderer system from the manager.
+ *
+ * @param system - The system to remove.
+ * @return True if the system was successfully removed, otherwise false.
+ */
+ bool RemoveSystem(System& system);
+
+ /**
+ * Free a session id when the system wants to shut down.
+ *
+ * @param session_id - The session id to free.
+ */
+ void ReleaseSessionId(s32 session_id);
+
+private:
+ /// Core system
+ Core::System& system;
+ /// Session ids, -1 when in use
+ std::array<s32, MaxRendererSessions> session_ids{};
+ /// Number of active renderers
+ u32 session_count{};
+ /// Lock for interacting with the sessions
+ mutable std::mutex session_lock{};
+ /// Regularly generates commands from the registered systems for the AudioRenderer
+ std::unique_ptr<SystemManager> system_manager{};
+};
+
+} // namespace AudioRenderer
+} // namespace AudioCore
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
deleted file mode 100644
index 7dba739b4..000000000
--- a/src/audio_core/audio_renderer.cpp
+++ /dev/null
@@ -1,340 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <limits>
-#include <vector>
-
-#include "audio_core/audio_out.h"
-#include "audio_core/audio_renderer.h"
-#include "audio_core/common.h"
-#include "audio_core/info_updater.h"
-#include "audio_core/voice_context.h"
-#include "common/logging/log.h"
-#include "common/settings.h"
-#include "core/core_timing.h"
-#include "core/memory.h"
-
-namespace {
-[[nodiscard]] static constexpr s16 ClampToS16(s32 value) {
- return static_cast<s16>(std::clamp(value, s32{std::numeric_limits<s16>::min()},
- s32{std::numeric_limits<s16>::max()}));
-}
-
-[[nodiscard]] static constexpr s16 Mix2To1(s16 l_channel, s16 r_channel) {
- // Mix 50% from left and 50% from right channel
- constexpr float l_mix_amount = 50.0f / 100.0f;
- constexpr float r_mix_amount = 50.0f / 100.0f;
- return ClampToS16(static_cast<s32>((static_cast<float>(l_channel) * l_mix_amount) +
- (static_cast<float>(r_channel) * r_mix_amount)));
-}
-
-[[maybe_unused, nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2(
- s16 fl_channel, s16 fr_channel, s16 fc_channel, [[maybe_unused]] s16 lf_channel, s16 bl_channel,
- s16 br_channel) {
- // Front channels are mixed 36.94%, Center channels are mixed to be 26.12% & the back channels
- // are mixed to be 36.94%
-
- constexpr float front_mix_amount = 36.94f / 100.0f;
- constexpr float center_mix_amount = 26.12f / 100.0f;
- constexpr float back_mix_amount = 36.94f / 100.0f;
-
- // Mix 50% from left and 50% from right channel
- const auto left = front_mix_amount * static_cast<float>(fl_channel) +
- center_mix_amount * static_cast<float>(fc_channel) +
- back_mix_amount * static_cast<float>(bl_channel);
-
- const auto right = front_mix_amount * static_cast<float>(fr_channel) +
- center_mix_amount * static_cast<float>(fc_channel) +
- back_mix_amount * static_cast<float>(br_channel);
-
- return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))};
-}
-
-[[nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2WithCoefficients(
- s16 fl_channel, s16 fr_channel, s16 fc_channel, s16 lf_channel, s16 bl_channel, s16 br_channel,
- const std::array<float_le, 4>& coeff) {
- const auto left =
- static_cast<float>(fl_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] +
- static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(bl_channel) * coeff[3];
-
- const auto right =
- static_cast<float>(fr_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] +
- static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(br_channel) * coeff[3];
-
- return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))};
-}
-
-} // namespace
-
-namespace AudioCore {
-constexpr s32 NUM_BUFFERS = 2;
-
-AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_,
- AudioCommon::AudioRendererParameter params,
- Stream::ReleaseCallback&& release_callback,
- std::size_t instance_number)
- : worker_params{params}, memory_pool_info(params.effect_count + params.voice_count * 4),
- voice_context(params.voice_count), effect_context(params.effect_count), mix_context(),
- sink_context(params.sink_count), splitter_context(),
- voices(params.voice_count), memory{memory_},
- command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
- memory),
- core_timing{core_timing_} {
- behavior_info.SetUserRevision(params.revision);
- splitter_context.Initialize(behavior_info, params.splitter_count,
- params.num_splitter_send_channels);
- mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count);
- audio_out = std::make_unique<AudioCore::AudioOut>();
- stream = audio_out->OpenStream(
- core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
- fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback));
- process_event = Core::Timing::CreateEvent(
- fmt::format("AudioRenderer-Instance{}-Process", instance_number),
- [this](std::uintptr_t, std::chrono::nanoseconds) { ReleaseAndQueueBuffers(); });
- for (s32 i = 0; i < NUM_BUFFERS; ++i) {
- QueueMixedBuffer(i);
- }
-}
-
-AudioRenderer::~AudioRenderer() = default;
-
-ResultCode AudioRenderer::Start() {
- audio_out->StartStream(stream);
- ReleaseAndQueueBuffers();
- return ResultSuccess;
-}
-
-ResultCode AudioRenderer::Stop() {
- audio_out->StopStream(stream);
- return ResultSuccess;
-}
-
-u32 AudioRenderer::GetSampleRate() const {
- return worker_params.sample_rate;
-}
-
-u32 AudioRenderer::GetSampleCount() const {
- return worker_params.sample_count;
-}
-
-u32 AudioRenderer::GetMixBufferCount() const {
- return worker_params.mix_buffer_count;
-}
-
-Stream::State AudioRenderer::GetStreamState() const {
- return stream->GetState();
-}
-
-ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
- std::vector<u8>& output_params) {
- std::scoped_lock lock{mutex};
- InfoUpdater info_updater{input_params, output_params, behavior_info};
-
- if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
- LOG_ERROR(Audio, "Failed to update behavior info input parameters");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- if (!info_updater.UpdateMemoryPools(memory_pool_info)) {
- LOG_ERROR(Audio, "Failed to update memory pool parameters");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- if (!info_updater.UpdateVoiceChannelResources(voice_context)) {
- LOG_ERROR(Audio, "Failed to update voice channel resource parameters");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) {
- LOG_ERROR(Audio, "Failed to update voice parameters");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- // TODO(ogniK): Deal with stopped audio renderer but updates still taking place
- if (!info_updater.UpdateEffects(effect_context, true)) {
- LOG_ERROR(Audio, "Failed to update effect parameters");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- if (behavior_info.IsSplitterSupported()) {
- if (!info_updater.UpdateSplitterInfo(splitter_context)) {
- LOG_ERROR(Audio, "Failed to update splitter parameters");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
- }
-
- const auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count,
- splitter_context, effect_context);
-
- if (mix_result.IsError()) {
- LOG_ERROR(Audio, "Failed to update mix parameters");
- return mix_result;
- }
-
- // TODO(ogniK): Sinks
- if (!info_updater.UpdateSinks(sink_context)) {
- LOG_ERROR(Audio, "Failed to update sink parameters");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- // TODO(ogniK): Performance buffer
- if (!info_updater.UpdatePerformanceBuffer()) {
- LOG_ERROR(Audio, "Failed to update performance buffer parameters");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- if (!info_updater.UpdateErrorInfo(behavior_info)) {
- LOG_ERROR(Audio, "Failed to update error info");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- if (behavior_info.IsElapsedFrameCountSupported()) {
- if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) {
- LOG_ERROR(Audio, "Failed to update renderer info");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
- }
- // TODO(ogniK): Statistics
-
- if (!info_updater.WriteOutputHeader()) {
- LOG_ERROR(Audio, "Failed to write output header");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- // TODO(ogniK): Check when all sections are implemented
-
- if (!info_updater.CheckConsumedSize()) {
- LOG_ERROR(Audio, "Audio buffers were not consumed!");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
- return ResultSuccess;
-}
-
-void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
- command_generator.PreCommand();
- // Clear mix buffers before our next operation
- command_generator.ClearMixBuffers();
-
- // If the splitter is not in use, sort our mixes
- if (!splitter_context.UsingSplitter()) {
- mix_context.SortInfo();
- }
- // Sort our voices
- voice_context.SortInfo();
-
- // Handle samples
- command_generator.GenerateVoiceCommands();
- command_generator.GenerateSubMixCommands();
- command_generator.GenerateFinalMixCommands();
-
- command_generator.PostCommand();
- // Base sample size
- std::size_t BUFFER_SIZE{worker_params.sample_count};
- // Samples, making sure to clear
- std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels(), 0);
-
- if (sink_context.InUse()) {
- const auto stream_channel_count = stream->GetNumChannels();
- const auto buffer_offsets = sink_context.OutputBuffers();
- const auto channel_count = buffer_offsets.size();
- const auto& final_mix = mix_context.GetFinalMixInfo();
- const auto& in_params = final_mix.GetInParams();
- std::vector<std::span<s32>> mix_buffers(channel_count);
- for (std::size_t i = 0; i < channel_count; i++) {
- mix_buffers[i] =
- command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]);
- }
-
- for (std::size_t i = 0; i < BUFFER_SIZE; i++) {
- if (channel_count == 1) {
- const auto sample = ClampToS16(mix_buffers[0][i]);
-
- // Place sample in all channels
- for (u32 channel = 0; channel < stream_channel_count; channel++) {
- buffer[i * stream_channel_count + channel] = sample;
- }
-
- if (stream_channel_count == 6) {
- // Output stream has a LF channel, mute it!
- buffer[i * stream_channel_count + 3] = 0;
- }
-
- } else if (channel_count == 2) {
- const auto l_sample = ClampToS16(mix_buffers[0][i]);
- const auto r_sample = ClampToS16(mix_buffers[1][i]);
- if (stream_channel_count == 1) {
- buffer[i * stream_channel_count + 0] = Mix2To1(l_sample, r_sample);
- } else if (stream_channel_count == 2) {
- buffer[i * stream_channel_count + 0] = l_sample;
- buffer[i * stream_channel_count + 1] = r_sample;
- } else if (stream_channel_count == 6) {
- buffer[i * stream_channel_count + 0] = l_sample;
- buffer[i * stream_channel_count + 1] = r_sample;
-
- // Combine both left and right channels to the center channel
- buffer[i * stream_channel_count + 2] = Mix2To1(l_sample, r_sample);
-
- buffer[i * stream_channel_count + 4] = l_sample;
- buffer[i * stream_channel_count + 5] = r_sample;
- }
-
- } else if (channel_count == 6) {
- const auto fl_sample = ClampToS16(mix_buffers[0][i]);
- const auto fr_sample = ClampToS16(mix_buffers[1][i]);
- const auto fc_sample = ClampToS16(mix_buffers[2][i]);
- const auto lf_sample = ClampToS16(mix_buffers[3][i]);
- const auto bl_sample = ClampToS16(mix_buffers[4][i]);
- const auto br_sample = ClampToS16(mix_buffers[5][i]);
-
- if (stream_channel_count == 1) {
- // Games seem to ignore the center channel half the time, we use the front left
- // and right channel for mixing as that's where majority of the audio goes
- buffer[i * stream_channel_count + 0] = Mix2To1(fl_sample, fr_sample);
- } else if (stream_channel_count == 2) {
- // Mix all channels into 2 channels
- const auto [left, right] = Mix6To2WithCoefficients(
- fl_sample, fr_sample, fc_sample, lf_sample, bl_sample, br_sample,
- sink_context.GetDownmixCoefficients());
- buffer[i * stream_channel_count + 0] = left;
- buffer[i * stream_channel_count + 1] = right;
- } else if (stream_channel_count == 6) {
- // Pass through
- buffer[i * stream_channel_count + 0] = fl_sample;
- buffer[i * stream_channel_count + 1] = fr_sample;
- buffer[i * stream_channel_count + 2] = fc_sample;
- buffer[i * stream_channel_count + 3] = lf_sample;
- buffer[i * stream_channel_count + 4] = bl_sample;
- buffer[i * stream_channel_count + 5] = br_sample;
- }
- }
- }
- }
-
- audio_out->QueueBuffer(stream, tag, std::move(buffer));
- elapsed_frame_count++;
- voice_context.UpdateStateByDspShared();
-}
-
-void AudioRenderer::ReleaseAndQueueBuffers() {
- if (!stream->IsPlaying()) {
- return;
- }
-
- {
- std::scoped_lock lock{mutex};
- const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)};
- for (const auto& tag : released_buffers) {
- QueueMixedBuffer(tag);
- }
- }
-
- const f32 sample_rate = static_cast<f32>(GetSampleRate());
- const f32 sample_count = static_cast<f32>(GetSampleCount());
- const f32 consume_rate = sample_rate / (sample_count * (sample_count / 240));
- const s32 ms = (1000 / static_cast<s32>(consume_rate)) - 1;
- const std::chrono::milliseconds next_event_time(std::max(ms / NUM_BUFFERS, 1));
- core_timing.ScheduleEvent(next_event_time, process_event, {});
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
deleted file mode 100644
index 88fdd13dd..000000000
--- a/src/audio_core/audio_renderer.h
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <memory>
-#include <mutex>
-#include <vector>
-
-#include "audio_core/behavior_info.h"
-#include "audio_core/command_generator.h"
-#include "audio_core/common.h"
-#include "audio_core/effect_context.h"
-#include "audio_core/memory_pool.h"
-#include "audio_core/mix_context.h"
-#include "audio_core/sink_context.h"
-#include "audio_core/splitter_context.h"
-#include "audio_core/stream.h"
-#include "audio_core/voice_context.h"
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/swap.h"
-#include "core/hle/result.h"
-
-namespace Core::Timing {
-class CoreTiming;
-}
-
-namespace Core::Memory {
-class Memory;
-}
-
-namespace AudioCore {
-using DSPStateHolder = std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>;
-
-class AudioOut;
-
-class AudioRenderer {
-public:
- AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
- AudioCommon::AudioRendererParameter params,
- Stream::ReleaseCallback&& release_callback, std::size_t instance_number);
- ~AudioRenderer();
-
- [[nodiscard]] ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
- std::vector<u8>& output_params);
- [[nodiscard]] ResultCode Start();
- [[nodiscard]] ResultCode Stop();
- void QueueMixedBuffer(Buffer::Tag tag);
- void ReleaseAndQueueBuffers();
- [[nodiscard]] u32 GetSampleRate() const;
- [[nodiscard]] u32 GetSampleCount() const;
- [[nodiscard]] u32 GetMixBufferCount() const;
- [[nodiscard]] Stream::State GetStreamState() const;
-
-private:
- BehaviorInfo behavior_info{};
-
- AudioCommon::AudioRendererParameter worker_params;
- std::vector<ServerMemoryPoolInfo> memory_pool_info;
- VoiceContext voice_context;
- EffectContext effect_context;
- MixContext mix_context;
- SinkContext sink_context;
- SplitterContext splitter_context;
- std::vector<VoiceState> voices;
- std::unique_ptr<AudioOut> audio_out;
- StreamPtr stream;
- Core::Memory::Memory& memory;
- CommandGenerator command_generator;
- std::size_t elapsed_frame_count{};
- Core::Timing::CoreTiming& core_timing;
- std::shared_ptr<Core::Timing::EventType> process_event;
- std::mutex mutex;
-};
-
-} // namespace AudioCore
diff --git a/src/audio_core/behavior_info.cpp b/src/audio_core/behavior_info.cpp
deleted file mode 100644
index 3c2e3e6f1..000000000
--- a/src/audio_core/behavior_info.cpp
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <cstring>
-#include "audio_core/behavior_info.h"
-#include "audio_core/common.h"
-#include "common/logging/log.h"
-
-namespace AudioCore {
-
-BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {}
-BehaviorInfo::~BehaviorInfo() = default;
-
-bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) {
- if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
-
- OutParams params{};
- std::memcpy(params.errors.data(), errors.data(), sizeof(ErrorInfo) * errors.size());
- params.error_count = static_cast<u32_le>(error_count);
- std::memcpy(buffer.data() + offset, &params, sizeof(OutParams));
- return true;
-}
-
-void BehaviorInfo::ClearError() {
- error_count = 0;
-}
-
-void BehaviorInfo::UpdateFlags(u64_le dest_flags) {
- flags = dest_flags;
-}
-
-void BehaviorInfo::SetUserRevision(u32_le revision) {
- user_revision = revision;
-}
-
-u32_le BehaviorInfo::GetUserRevision() const {
- return user_revision;
-}
-
-u32_le BehaviorInfo::GetProcessRevision() const {
- return process_revision;
-}
-
-bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
- return AudioCommon::IsRevisionSupported(2, user_revision);
-}
-
-bool BehaviorInfo::IsSplitterSupported() const {
- return AudioCommon::IsRevisionSupported(2, user_revision);
-}
-
-bool BehaviorInfo::IsLongSizePreDelaySupported() const {
- return AudioCommon::IsRevisionSupported(3, user_revision);
-}
-
-bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const {
- return AudioCommon::IsRevisionSupported(5, user_revision);
-}
-
-bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const {
- return AudioCommon::IsRevisionSupported(4, user_revision);
-}
-
-bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const {
- return AudioCommon::IsRevisionSupported(1, user_revision);
-}
-
-bool BehaviorInfo::IsElapsedFrameCountSupported() const {
- return AudioCommon::IsRevisionSupported(5, user_revision);
-}
-
-bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const {
- return (flags & 1) != 0;
-}
-
-bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
- return AudioCommon::IsRevisionSupported(5, user_revision);
-}
-
-bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
- return AudioCommon::IsRevisionSupported(5, user_revision);
-}
-
-bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
- return AudioCommon::IsRevisionSupported(5, user_revision);
-}
-
-bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
- return AudioCommon::IsRevisionSupported(7, user_revision);
-}
-
-bool BehaviorInfo::IsSplitterBugFixed() const {
- return AudioCommon::IsRevisionSupported(5, user_revision);
-}
-
-void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) {
- dst.error_count = static_cast<u32>(error_count);
- std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin());
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/behavior_info.h b/src/audio_core/behavior_info.h
deleted file mode 100644
index 5a96bf75e..000000000
--- a/src/audio_core/behavior_info.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-
-#include <vector>
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/swap.h"
-
-namespace AudioCore {
-class BehaviorInfo {
-public:
- struct ErrorInfo {
- u32_le result{};
- INSERT_PADDING_WORDS(1);
- u64_le result_info{};
- };
- static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
-
- struct InParams {
- u32_le revision{};
- u32_le padding{};
- u64_le flags{};
- };
- static_assert(sizeof(InParams) == 0x10, "InParams is an invalid size");
-
- struct OutParams {
- std::array<ErrorInfo, 10> errors{};
- u32_le error_count{};
- INSERT_PADDING_BYTES(12);
- };
- static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size");
-
- explicit BehaviorInfo();
- ~BehaviorInfo();
-
- bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
-
- void ClearError();
- void UpdateFlags(u64_le dest_flags);
- void SetUserRevision(u32_le revision);
- [[nodiscard]] u32_le GetUserRevision() const;
- [[nodiscard]] u32_le GetProcessRevision() const;
-
- [[nodiscard]] bool IsAdpcmLoopContextBugFixed() const;
- [[nodiscard]] bool IsSplitterSupported() const;
- [[nodiscard]] bool IsLongSizePreDelaySupported() const;
- [[nodiscard]] bool IsAudioRendererProcessingTimeLimit80PercentSupported() const;
- [[nodiscard]] bool IsAudioRendererProcessingTimeLimit75PercentSupported() const;
- [[nodiscard]] bool IsAudioRendererProcessingTimeLimit70PercentSupported() const;
- [[nodiscard]] bool IsElapsedFrameCountSupported() const;
- [[nodiscard]] bool IsMemoryPoolForceMappingEnabled() const;
- [[nodiscard]] bool IsFlushVoiceWaveBuffersSupported() const;
- [[nodiscard]] bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
- [[nodiscard]] bool IsVoicePitchAndSrcSkippedSupported() const;
- [[nodiscard]] bool IsMixInParameterDirtyOnlyUpdateSupported() const;
- [[nodiscard]] bool IsSplitterBugFixed() const;
- void CopyErrorInfo(OutParams& dst);
-
-private:
- u32_le process_revision{};
- u32_le user_revision{};
- u64_le flags{};
- std::array<ErrorInfo, 10> errors{};
- std::size_t error_count{};
-};
-
-} // namespace AudioCore
diff --git a/src/audio_core/buffer.h b/src/audio_core/buffer.h
deleted file mode 100644
index ccc46ef82..000000000
--- a/src/audio_core/buffer.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <vector>
-
-#include "common/common_types.h"
-
-namespace AudioCore {
-
-/**
- * Represents a buffer of audio samples to be played in an audio stream
- */
-class Buffer {
-public:
- using Tag = u64;
-
- Buffer(Tag tag_, std::vector<s16>&& samples_) : tag{tag_}, samples{std::move(samples_)} {}
-
- /// Returns the raw audio data for the buffer
- std::vector<s16>& GetSamples() {
- return samples;
- }
-
- /// Returns the raw audio data for the buffer
- const std::vector<s16>& GetSamples() const {
- return samples;
- }
-
- /// Returns the buffer tag, this is provided by the game to the audout service
- Tag GetTag() const {
- return tag;
- }
-
-private:
- Tag tag;
- std::vector<s16> samples;
-};
-
-using BufferPtr = std::shared_ptr<Buffer>;
-
-} // namespace AudioCore
diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp
deleted file mode 100644
index 2fb91c13a..000000000
--- a/src/audio_core/codec.cpp
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-
-#include "audio_core/codec.h"
-
-namespace AudioCore::Codec {
-
-std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM_Coeff& coeff,
- ADPCMState& state) {
- // GC-ADPCM with scale factor and variable coefficients.
- // Frames are 8 bytes long containing 14 samples each.
- // Samples are 4 bits (one nibble) long.
-
- constexpr std::size_t FRAME_LEN = 8;
- constexpr std::size_t SAMPLES_PER_FRAME = 14;
- static constexpr std::array<int, 16> SIGNED_NIBBLES{
- 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
- };
-
- const std::size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME;
- const std::size_t ret_size =
- sample_count % 2 == 0 ? sample_count : sample_count + 1; // Ensure multiple of two.
- std::vector<s16> ret(ret_size);
-
- int yn1 = state.yn1, yn2 = state.yn2;
-
- const std::size_t NUM_FRAMES =
- (sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up.
- for (std::size_t framei = 0; framei < NUM_FRAMES; framei++) {
- const int frame_header = data[framei * FRAME_LEN];
- const int scale = 1 << (frame_header & 0xF);
- const int idx = (frame_header >> 4) & 0x7;
-
- // Coefficients are fixed point with 11 bits fractional part.
- const int coef1 = coeff[idx * 2 + 0];
- const int coef2 = coeff[idx * 2 + 1];
-
- // Decodes an audio sample. One nibble produces one sample.
- const auto decode_sample = [&](const int nibble) -> s16 {
- const int xn = nibble * scale;
- // We first transform everything into 11 bit fixed point, perform the second order
- // digital filter, then transform back.
- // 0x400 == 0.5 in 11 bit fixed point.
- // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
- int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
- // Clamp to output range.
- val = std::clamp<s32>(val, -32768, 32767);
- // Advance output feedback.
- yn2 = yn1;
- yn1 = val;
- return static_cast<s16>(val);
- };
-
- std::size_t outputi = framei * SAMPLES_PER_FRAME;
- std::size_t datai = framei * FRAME_LEN + 1;
- for (std::size_t i = 0; i < SAMPLES_PER_FRAME && outputi < sample_count; i += 2) {
- const s16 sample1 = decode_sample(SIGNED_NIBBLES[data[datai] >> 4]);
- ret[outputi] = sample1;
- outputi++;
-
- const s16 sample2 = decode_sample(SIGNED_NIBBLES[data[datai] & 0xF]);
- ret[outputi] = sample2;
- outputi++;
-
- datai++;
- }
- }
-
- state.yn1 = static_cast<s16>(yn1);
- state.yn2 = static_cast<s16>(yn2);
-
- return ret;
-}
-
-} // namespace AudioCore::Codec
diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h
deleted file mode 100644
index 9507abb1b..000000000
--- a/src/audio_core/codec.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <vector>
-
-#include "common/common_types.h"
-
-namespace AudioCore::Codec {
-
-enum class PcmFormat : u32 {
- Invalid = 0,
- Int8 = 1,
- Int16 = 2,
- Int24 = 3,
- Int32 = 4,
- PcmFloat = 5,
- Adpcm = 6,
-};
-
-/// See: Codec::DecodeADPCM
-struct ADPCMState {
- // Two historical samples from previous processed buffer,
- // required for ADPCM decoding
- s16 yn1; ///< y[n-1]
- s16 yn2; ///< y[n-2]
-};
-
-using ADPCM_Coeff = std::array<s16, 16>;
-
-/**
- * @param data Pointer to buffer that contains ADPCM data to decode
- * @param size Size of buffer in bytes
- * @param coeff ADPCM coefficients
- * @param state ADPCM state, this is updated with new state
- * @return Decoded stereo signed PCM16 data, sample_count in length
- */
-std::vector<s16> DecodeADPCM(const u8* data, std::size_t size, const ADPCM_Coeff& coeff,
- ADPCMState& state);
-
-}; // namespace AudioCore::Codec
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp
deleted file mode 100644
index 830af46ad..000000000
--- a/src/audio_core/command_generator.cpp
+++ /dev/null
@@ -1,1370 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <cmath>
-#include <numbers>
-
-#include "audio_core/algorithm/interpolate.h"
-#include "audio_core/command_generator.h"
-#include "audio_core/effect_context.h"
-#include "audio_core/mix_context.h"
-#include "audio_core/voice_context.h"
-#include "common/common_types.h"
-#include "core/memory.h"
-
-namespace AudioCore {
-namespace {
-constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
-constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL;
-using DelayLineTimes = std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>;
-
-constexpr DelayLineTimes FDN_MIN_DELAY_LINE_TIMES{5.0f, 6.0f, 13.0f, 14.0f};
-constexpr DelayLineTimes FDN_MAX_DELAY_LINE_TIMES{45.704f, 82.782f, 149.94f, 271.58f};
-constexpr DelayLineTimes DECAY0_MAX_DELAY_LINE_TIMES{17.0f, 13.0f, 9.0f, 7.0f};
-constexpr DelayLineTimes DECAY1_MAX_DELAY_LINE_TIMES{19.0f, 11.0f, 10.0f, 6.0f};
-constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_TAP_TIMES{
- 0.017136f, 0.059154f, 0.161733f, 0.390186f, 0.425262f, 0.455411f, 0.689737f,
- 0.745910f, 0.833844f, 0.859502f, 0.000000f, 0.075024f, 0.168788f, 0.299901f,
- 0.337443f, 0.371903f, 0.599011f, 0.716741f, 0.817859f, 0.851664f};
-constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_GAIN{
- 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f,
- 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f,
- 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f};
-
-template <std::size_t N>
-void ApplyMix(std::span<s32> output, std::span<const s32> input, s32 gain, s32 sample_count) {
- for (std::size_t i = 0; i < static_cast<std::size_t>(sample_count); i += N) {
- for (std::size_t j = 0; j < N; j++) {
- output[i + j] +=
- static_cast<s32>((static_cast<s64>(input[i + j]) * gain + 0x4000) >> 15);
- }
- }
-}
-
-s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, float gain, float delta,
- s32 sample_count) {
- // XC2 passes in NaN mix volumes, causing further issues as we handle everything as s32 rather
- // than float, so the NaN propogation is lost. As the samples get further modified for
- // volume etc, they can get out of NaN range, so a later heuristic for catching this is
- // more difficult. Handle it here by setting these samples to silence.
- if (std::isnan(gain)) {
- gain = 0.0f;
- delta = 0.0f;
- }
-
- s32 x = 0;
- for (s32 i = 0; i < sample_count; i++) {
- x = static_cast<s32>(static_cast<float>(input[i]) * gain);
- output[i] += x;
- gain += delta;
- }
- return x;
-}
-
-void ApplyGain(std::span<s32> output, std::span<const s32> input, s32 gain, s32 delta,
- s32 sample_count) {
- for (s32 i = 0; i < sample_count; i++) {
- output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
- gain += delta;
- }
-}
-
-void ApplyGainWithoutDelta(std::span<s32> output, std::span<const s32> input, s32 gain,
- s32 sample_count) {
- for (s32 i = 0; i < sample_count; i++) {
- output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
- }
-}
-
-s32 ApplyMixDepop(std::span<s32> output, s32 first_sample, s32 delta, s32 sample_count) {
- const bool positive = first_sample > 0;
- auto final_sample = std::abs(first_sample);
- for (s32 i = 0; i < sample_count; i++) {
- final_sample = static_cast<s32>((static_cast<s64>(final_sample) * delta) >> 15);
- if (positive) {
- output[i] += final_sample;
- } else {
- output[i] -= final_sample;
- }
- }
- if (positive) {
- return final_sample;
- } else {
- return -final_sample;
- }
-}
-
-float Pow10(float x) {
- if (x >= 0.0f) {
- return 1.0f;
- } else if (x <= -5.3f) {
- return 0.0f;
- }
- return std::pow(10.0f, x);
-}
-
-float SinD(float degrees) {
- return std::sin(degrees * std::numbers::pi_v<float> / 180.0f);
-}
-
-float CosD(float degrees) {
- return std::cos(degrees * std::numbers::pi_v<float> / 180.0f);
-}
-
-float ToFloat(s32 sample) {
- return static_cast<float>(sample) / 65536.f;
-}
-
-s32 ToS32(float sample) {
- constexpr auto min = -8388608.0f;
- constexpr auto max = 8388607.f;
- float rescaled_sample = sample * 65536.0f;
- if (rescaled_sample < min) {
- rescaled_sample = min;
- }
- if (rescaled_sample > max) {
- rescaled_sample = max;
- }
- return static_cast<s32>(rescaled_sample);
-}
-
-constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-
-constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
- 1, 1, 1, 0, 0, 0, 0, 1, 1, 1};
-
-constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2,
- 1, 1, 1, 0, 0, 0, 0, 3, 3, 3};
-
-constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2,
- 1, 1, 1, 0, 0, 0, 0, 3, 3, 3};
-
-template <std::size_t CHANNEL_COUNT>
-void ApplyReverbGeneric(
- I3dl2ReverbState& state,
- const std::array<std::span<const s32>, AudioCommon::MAX_CHANNEL_COUNT>& input,
- const std::array<std::span<s32>, AudioCommon::MAX_CHANNEL_COUNT>& output, s32 sample_count) {
-
- auto GetTapLookup = []() {
- if constexpr (CHANNEL_COUNT == 1) {
- return REVERB_TAP_INDEX_1CH;
- } else if constexpr (CHANNEL_COUNT == 2) {
- return REVERB_TAP_INDEX_2CH;
- } else if constexpr (CHANNEL_COUNT == 4) {
- return REVERB_TAP_INDEX_4CH;
- } else if constexpr (CHANNEL_COUNT == 6) {
- return REVERB_TAP_INDEX_6CH;
- }
- };
-
- const auto& tap_index_lut = GetTapLookup();
- for (s32 sample = 0; sample < sample_count; sample++) {
- std::array<f32, CHANNEL_COUNT> out_samples{};
- std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fsamp{};
- std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> mixed{};
- std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> osamp{};
-
- // Mix everything into a single sample
- s32 temp_mixed_sample = 0;
- for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
- temp_mixed_sample += input[i][sample];
- }
- const auto current_sample = ToFloat(temp_mixed_sample);
- const auto early_tap = state.early_delay_line.TapOut(state.early_to_late_taps);
-
- for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_TAPS; i++) {
- const auto tapped_samp =
- state.early_delay_line.TapOut(state.early_tap_steps[i]) * EARLY_GAIN[i];
- out_samples[tap_index_lut[i]] += tapped_samp;
-
- if constexpr (CHANNEL_COUNT == 6) {
- // handle lfe
- out_samples[5] += tapped_samp;
- }
- }
-
- state.lowpass_0 = current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1;
- state.early_delay_line.Tick(state.lowpass_0);
-
- for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
- out_samples[i] *= state.early_gain;
- }
-
- // Two channel seems to apply a latet gain, we require to save this
- f32 filter{};
- for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
- filter = state.fdn_delay_line[i].GetOutputSample();
- const auto computed = filter * state.lpf_coefficients[0][i] + state.shelf_filter[i];
- state.shelf_filter[i] =
- filter * state.lpf_coefficients[1][i] + computed * state.lpf_coefficients[2][i];
- fsamp[i] = computed;
- }
-
- // Mixing matrix
- mixed[0] = fsamp[1] + fsamp[2];
- mixed[1] = -fsamp[0] - fsamp[3];
- mixed[2] = fsamp[0] - fsamp[3];
- mixed[3] = fsamp[1] - fsamp[2];
-
- if constexpr (CHANNEL_COUNT == 2) {
- for (auto& mix : mixed) {
- mix *= (filter * state.late_gain);
- }
- }
-
- for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
- const auto late = early_tap * state.late_gain;
- osamp[i] = state.decay_delay_line0[i].Tick(late + mixed[i]);
- osamp[i] = state.decay_delay_line1[i].Tick(osamp[i]);
- state.fdn_delay_line[i].Tick(osamp[i]);
- }
-
- if constexpr (CHANNEL_COUNT == 1) {
- output[0][sample] = ToS32(state.dry_gain * ToFloat(input[0][sample]) +
- (out_samples[0] + osamp[0] + osamp[1]));
- } else if constexpr (CHANNEL_COUNT == 2 || CHANNEL_COUNT == 4) {
- for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
- output[i][sample] =
- ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i]));
- }
- } else if constexpr (CHANNEL_COUNT == 6) {
- const auto temp_center = state.center_delay_line.Tick(0.5f * (osamp[2] - osamp[3]));
- for (std::size_t i = 0; i < 4; i++) {
- output[i][sample] =
- ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i]));
- }
- output[4][sample] =
- ToS32(state.dry_gain * ToFloat(input[4][sample]) + (out_samples[4] + temp_center));
- output[5][sample] =
- ToS32(state.dry_gain * ToFloat(input[5][sample]) + (out_samples[5] + osamp[3]));
- }
- }
-}
-
-} // namespace
-
-CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_,
- VoiceContext& voice_context_, MixContext& mix_context_,
- SplitterContext& splitter_context_,
- EffectContext& effect_context_, Core::Memory::Memory& memory_)
- : worker_params(worker_params_), voice_context(voice_context_), mix_context(mix_context_),
- splitter_context(splitter_context_), effect_context(effect_context_), memory(memory_),
- mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
- worker_params.sample_count),
- sample_buffer(MIX_BUFFER_SIZE),
- depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
- worker_params.sample_count) {}
-CommandGenerator::~CommandGenerator() = default;
-
-void CommandGenerator::ClearMixBuffers() {
- std::fill(mix_buffer.begin(), mix_buffer.end(), 0);
- std::fill(sample_buffer.begin(), sample_buffer.end(), 0);
- // std::fill(depop_buffer.begin(), depop_buffer.end(), 0);
-}
-
-void CommandGenerator::GenerateVoiceCommands() {
- if (dumping_frame) {
- LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands");
- }
- // Grab all our voices
- const auto voice_count = voice_context.GetVoiceCount();
- for (std::size_t i = 0; i < voice_count; i++) {
- auto& voice_info = voice_context.GetSortedInfo(i);
- // Update voices and check if we should queue them
- if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) {
- continue;
- }
-
- // Queue our voice
- GenerateVoiceCommand(voice_info);
- }
- // Update our splitters
- splitter_context.UpdateInternalState();
-}
-
-void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) {
- auto& in_params = voice_info.GetInParams();
- const auto channel_count = in_params.channel_count;
-
- for (s32 channel = 0; channel < channel_count; channel++) {
- const auto resource_id = in_params.voice_channel_resource_id[channel];
- auto& dsp_state = voice_context.GetDspSharedState(resource_id);
- auto& channel_resource = voice_context.GetChannelResource(resource_id);
-
- // Decode our samples for our channel
- GenerateDataSourceCommand(voice_info, dsp_state, channel);
-
- if (in_params.should_depop) {
- in_params.last_volume = 0.0f;
- } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER ||
- in_params.mix_id != AudioCommon::NO_MIX) {
- // Apply a biquad filter if needed
- GenerateBiquadFilterCommandForVoice(voice_info, dsp_state,
- worker_params.mix_buffer_count, channel);
- // Base voice volume ramping
- GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel,
- in_params.node_id);
- in_params.last_volume = in_params.volume;
-
- if (in_params.mix_id != AudioCommon::NO_MIX) {
- // If we're using a mix id
- auto& mix_info = mix_context.GetInfo(in_params.mix_id);
- const auto& dest_mix_params = mix_info.GetInParams();
-
- // Voice Mixing
- GenerateVoiceMixCommand(
- channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(),
- dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
- worker_params.mix_buffer_count + channel, in_params.node_id);
-
- // Update last mix volumes
- channel_resource.UpdateLastMixVolumes();
- } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
- s32 base = channel;
- while (auto* destination_data =
- GetDestinationData(in_params.splitter_info_id, base)) {
- base += channel_count;
-
- if (!destination_data->IsConfigured()) {
- continue;
- }
- if (destination_data->GetMixId() >= static_cast<int>(mix_context.GetCount())) {
- continue;
- }
-
- const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId());
- const auto& dest_mix_params = mix_info.GetInParams();
- GenerateVoiceMixCommand(
- destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(),
- dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
- worker_params.mix_buffer_count + channel, in_params.node_id);
- destination_data->MarkDirty();
- }
- }
- // Update biquad filter enabled states
- for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
- in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled;
- }
- }
- }
-}
-
-void CommandGenerator::GenerateSubMixCommands() {
- const auto mix_count = mix_context.GetCount();
- for (std::size_t i = 0; i < mix_count; i++) {
- auto& mix_info = mix_context.GetSortedInfo(i);
- const auto& in_params = mix_info.GetInParams();
- if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) {
- continue;
- }
- GenerateSubMixCommand(mix_info);
- }
-}
-
-void CommandGenerator::GenerateFinalMixCommands() {
- GenerateFinalMixCommand();
-}
-
-void CommandGenerator::PreCommand() {
- if (!dumping_frame) {
- return;
- }
- for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) {
- const auto& base = splitter_context.GetInfo(i);
- std::string graph = fmt::format("b[{}]", i);
- const auto* head = base.GetHead();
- while (head != nullptr) {
- graph += fmt::format("->{}", head->GetMixId());
- head = head->GetNextDestination();
- }
- LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph);
- }
-}
-
-void CommandGenerator::PostCommand() {
- if (!dumping_frame) {
- return;
- }
- dumping_frame = false;
-}
-
-void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
- s32 channel) {
- const auto& in_params = voice_info.GetInParams();
- const auto depop = in_params.should_depop;
-
- if (depop) {
- if (in_params.mix_id != AudioCommon::NO_MIX) {
- auto& mix_info = mix_context.GetInfo(in_params.mix_id);
- const auto& mix_in = mix_info.GetInParams();
- GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
- } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
- s32 index{};
- while (const auto* destination =
- GetDestinationData(in_params.splitter_info_id, index++)) {
- if (!destination->IsConfigured()) {
- continue;
- }
- auto& mix_info = mix_context.GetInfo(destination->GetMixId());
- const auto& mix_in = mix_info.GetInParams();
- GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
- }
- }
- } else {
- switch (in_params.sample_format) {
- case SampleFormat::Pcm8:
- case SampleFormat::Pcm16:
- case SampleFormat::Pcm32:
- case SampleFormat::PcmFloat:
- DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel,
- worker_params.sample_rate, worker_params.sample_count,
- in_params.node_id);
- break;
- case SampleFormat::Adpcm:
- ASSERT(channel == 0 && in_params.channel_count == 1);
- DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0,
- worker_params.sample_rate, worker_params.sample_count,
- in_params.node_id);
- break;
- default:
- UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
- }
- }
-}
-
-void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info,
- VoiceState& dsp_state,
- [[maybe_unused]] s32 mix_buffer_count,
- [[maybe_unused]] s32 channel) {
- for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
- const auto& in_params = voice_info.GetInParams();
- auto& biquad_filter = in_params.biquad_filter[i];
- // Check if biquad filter is actually used
- if (!biquad_filter.enabled) {
- continue;
- }
-
- // Reinitialize our biquad filter state if it was enabled previously
- if (!in_params.was_biquad_filter_enabled[i]) {
- dsp_state.biquad_filter_state.fill(0);
- }
-
- // Generate biquad filter
- // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
- // dsp_state.biquad_filter_state,
- // mix_buffer_count + channel, mix_buffer_count + channel,
- // worker_params.sample_count, voice_info.GetInParams().node_id);
- }
-}
-
-void CommandGenerator::GenerateBiquadFilterCommand([[maybe_unused]] s32 mix_buffer_id,
- const BiquadFilterParameter& params,
- std::array<s64, 2>& state,
- std::size_t input_offset,
- std::size_t output_offset, s32 sample_count,
- s32 node_id) {
- if (dumping_frame) {
- LOG_DEBUG(Audio,
- "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, "
- "input_mix_buffer={}, output_mix_buffer={}",
- node_id, input_offset, output_offset);
- }
- std::span<const s32> input = GetMixBuffer(input_offset);
- std::span<s32> output = GetMixBuffer(output_offset);
-
- // Biquad filter parameters
- const auto [n0, n1, n2] = params.numerator;
- const auto [d0, d1] = params.denominator;
-
- // Biquad filter states
- auto [s0, s1] = state;
-
- constexpr s64 int32_min = std::numeric_limits<s32>::min();
- constexpr s64 int32_max = std::numeric_limits<s32>::max();
-
- for (int i = 0; i < sample_count; ++i) {
- const auto sample = static_cast<s64>(input[i]);
- const auto f = (sample * n0 + s0 + 0x4000) >> 15;
- const auto y = std::clamp(f, int32_min, int32_max);
- s0 = sample * n1 + y * d0 + s1;
- s1 = sample * n2 + y * d1;
- output[i] = static_cast<s32>(y);
- }
-
- state = {s0, s1};
-}
-
-void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state,
- std::size_t mix_buffer_count,
- std::size_t mix_buffer_offset) {
- for (std::size_t i = 0; i < mix_buffer_count; i++) {
- auto& sample = dsp_state.previous_samples[i];
- if (sample != 0) {
- depop_buffer[mix_buffer_offset + i] += sample;
- sample = 0;
- }
- }
-}
-
-void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
- std::size_t mix_buffer_offset,
- s32 sample_rate) {
- const std::size_t end_offset =
- std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount());
- const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB;
- for (std::size_t i = mix_buffer_offset; i < end_offset; i++) {
- if (depop_buffer[i] == 0) {
- continue;
- }
-
- depop_buffer[i] =
- ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count);
- }
-}
-
-void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
- const std::size_t effect_count = effect_context.GetCount();
- const auto buffer_offset = mix_info.GetInParams().buffer_offset;
- for (std::size_t i = 0; i < effect_count; i++) {
- const auto index = mix_info.GetEffectOrder(i);
- if (index == AudioCommon::NO_EFFECT_ORDER) {
- break;
- }
- auto* info = effect_context.GetInfo(index);
- const auto type = info->GetType();
-
- // TODO(ogniK): Finish remaining effects
- switch (type) {
- case EffectType::Aux:
- GenerateAuxCommand(buffer_offset, info, info->IsEnabled());
- break;
- case EffectType::I3dl2Reverb:
- GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled());
- break;
- case EffectType::BiquadFilter:
- GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled());
- break;
- default:
- break;
- }
-
- info->UpdateForCommandGeneration();
- }
-}
-
-void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
- bool enabled) {
- auto* reverb = dynamic_cast<EffectI3dl2Reverb*>(info);
- const auto& params = reverb->GetParams();
- auto& state = reverb->GetState();
- const auto channel_count = params.channel_count;
-
- if (channel_count != 1 && channel_count != 2 && channel_count != 4 && channel_count != 6) {
- return;
- }
-
- std::array<std::span<const s32>, AudioCommon::MAX_CHANNEL_COUNT> input{};
- std::array<std::span<s32>, AudioCommon::MAX_CHANNEL_COUNT> output{};
-
- const auto status = params.status;
- for (s32 i = 0; i < channel_count; i++) {
- input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]);
- output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]);
- }
-
- if (enabled) {
- if (status == ParameterStatus::Initialized) {
- InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer());
- } else if (status == ParameterStatus::Updating) {
- UpdateI3dl2Reverb(reverb->GetParams(), state, false);
- }
- }
-
- if (enabled) {
- switch (channel_count) {
- case 1:
- ApplyReverbGeneric<1>(state, input, output, worker_params.sample_count);
- break;
- case 2:
- ApplyReverbGeneric<2>(state, input, output, worker_params.sample_count);
- break;
- case 4:
- ApplyReverbGeneric<4>(state, input, output, worker_params.sample_count);
- break;
- case 6:
- ApplyReverbGeneric<6>(state, input, output, worker_params.sample_count);
- break;
- }
- } else {
- for (s32 i = 0; i < channel_count; i++) {
- // Only copy if the buffer input and output do not match!
- if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) {
- std::memcpy(output[i].data(), input[i].data(),
- worker_params.sample_count * sizeof(s32));
- }
- }
- }
-}
-
-void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info,
- bool enabled) {
- if (!enabled) {
- return;
- }
- const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams();
- const auto channel_count = params.channel_count;
- for (s32 i = 0; i < channel_count; i++) {
- // TODO(ogniK): Actually implement biquad filter
- if (params.input[i] != params.output[i]) {
- std::span<const s32> input = GetMixBuffer(mix_buffer_offset + params.input[i]);
- std::span<s32> output = GetMixBuffer(mix_buffer_offset + params.output[i]);
- ApplyMix<1>(output, input, 32768, worker_params.sample_count);
- }
- }
-}
-
-void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) {
- auto* aux = dynamic_cast<EffectAuxInfo*>(info);
- const auto& params = aux->GetParams();
- if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) {
- const auto max_channels = params.count;
- u32 offset{};
- for (u32 channel = 0; channel < max_channels; channel++) {
- u32 write_count = 0;
- if (channel == (max_channels - 1)) {
- write_count = offset + worker_params.sample_count;
- }
-
- const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset;
- const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset;
-
- if (enabled) {
- AuxInfoDSP send_info{};
- AuxInfoDSP recv_info{};
- memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
- memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
-
- WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count,
- GetMixBuffer(input_index), worker_params.sample_count, offset,
- write_count);
- memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
-
- const auto samples_read = ReadAuxBuffer(
- recv_info, aux->GetRecvBuffer(), params.sample_count,
- GetMixBuffer(output_index), worker_params.sample_count, offset, write_count);
- memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
-
- if (samples_read != static_cast<int>(worker_params.sample_count) &&
- samples_read <= params.sample_count) {
- std::memset(GetMixBuffer(output_index).data(), 0,
- params.sample_count - samples_read);
- }
- } else {
- AuxInfoDSP empty{};
- memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP));
- memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP));
- if (output_index != input_index) {
- std::memcpy(GetMixBuffer(output_index).data(), GetMixBuffer(input_index).data(),
- worker_params.sample_count * sizeof(s32));
- }
- }
-
- offset += worker_params.sample_count;
- }
- }
-}
-
-ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) {
- if (splitter_id == AudioCommon::NO_SPLITTER) {
- return nullptr;
- }
- return splitter_context.GetDestinationData(splitter_id, index);
-}
-
-s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
- std::span<const s32> data, u32 sample_count, u32 write_offset,
- u32 write_count) {
- if (max_samples == 0) {
- return 0;
- }
- u32 offset = dsp_info.write_offset + write_offset;
- if (send_buffer == 0 || offset > max_samples) {
- return 0;
- }
-
- s32 data_offset{};
- u32 remaining = sample_count;
- while (remaining > 0) {
- // Get position in buffer
- const auto base = send_buffer + (offset * sizeof(u32));
- const auto samples_to_grab = std::min(max_samples - offset, remaining);
- // Write to output
- memory.WriteBlock(base, (data.data() + data_offset), samples_to_grab * sizeof(u32));
- offset = (offset + samples_to_grab) % max_samples;
- remaining -= samples_to_grab;
- data_offset += samples_to_grab;
- }
-
- if (write_count != 0) {
- dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples;
- }
- return sample_count;
-}
-
-s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
- std::span<s32> out_data, u32 sample_count, u32 read_offset,
- u32 read_count) {
- if (max_samples == 0) {
- return 0;
- }
-
- u32 offset = recv_info.read_offset + read_offset;
- if (recv_buffer == 0 || offset > max_samples) {
- return 0;
- }
-
- u32 remaining = sample_count;
- s32 data_offset{};
- while (remaining > 0) {
- const auto base = recv_buffer + (offset * sizeof(u32));
- const auto samples_to_grab = std::min(max_samples - offset, remaining);
- std::vector<s32> buffer(samples_to_grab);
- memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32));
- std::memcpy(out_data.data() + data_offset, buffer.data(), buffer.size() * sizeof(u32));
- offset = (offset + samples_to_grab) % max_samples;
- remaining -= samples_to_grab;
- data_offset += samples_to_grab;
- }
-
- if (read_count != 0) {
- recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples;
- }
- return sample_count;
-}
-
-void CommandGenerator::InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
- std::vector<u8>& work_buffer) {
- // Reset state
- state.lowpass_0 = 0.0f;
- state.lowpass_1 = 0.0f;
- state.lowpass_2 = 0.0f;
-
- state.early_delay_line.Reset();
- state.early_tap_steps.fill(0);
- state.early_gain = 0.0f;
- state.late_gain = 0.0f;
- state.early_to_late_taps = 0;
- for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
- state.fdn_delay_line[i].Reset();
- state.decay_delay_line0[i].Reset();
- state.decay_delay_line1[i].Reset();
- }
- state.last_reverb_echo = 0.0f;
- state.center_delay_line.Reset();
- for (auto& coef : state.lpf_coefficients) {
- coef.fill(0.0f);
- }
- state.shelf_filter.fill(0.0f);
- state.dry_gain = 0.0f;
-
- const auto sample_rate = info.sample_rate / 1000;
- f32* work_buffer_ptr = reinterpret_cast<f32*>(work_buffer.data());
-
- s32 delay_samples{};
- for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
- delay_samples =
- AudioCommon::CalculateDelaySamples(sample_rate, FDN_MAX_DELAY_LINE_TIMES[i]);
- state.fdn_delay_line[i].Initialize(delay_samples, work_buffer_ptr);
- work_buffer_ptr += delay_samples + 1;
-
- delay_samples =
- AudioCommon::CalculateDelaySamples(sample_rate, DECAY0_MAX_DELAY_LINE_TIMES[i]);
- state.decay_delay_line0[i].Initialize(delay_samples, 0.0f, work_buffer_ptr);
- work_buffer_ptr += delay_samples + 1;
-
- delay_samples =
- AudioCommon::CalculateDelaySamples(sample_rate, DECAY1_MAX_DELAY_LINE_TIMES[i]);
- state.decay_delay_line1[i].Initialize(delay_samples, 0.0f, work_buffer_ptr);
- work_buffer_ptr += delay_samples + 1;
- }
- delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 5.0f);
- state.center_delay_line.Initialize(delay_samples, work_buffer_ptr);
- work_buffer_ptr += delay_samples + 1;
-
- delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 400.0f);
- state.early_delay_line.Initialize(delay_samples, work_buffer_ptr);
-
- UpdateI3dl2Reverb(info, state, true);
-}
-
-void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
- bool should_clear) {
-
- state.dry_gain = info.dry_gain;
- state.shelf_filter.fill(0.0f);
- state.lowpass_0 = 0.0f;
- state.early_gain = Pow10(std::min(info.room + info.reflection, 5000.0f) / 2000.0f);
- state.late_gain = Pow10(std::min(info.room + info.reverb, 5000.0f) / 2000.0f);
-
- const auto sample_rate = info.sample_rate / 1000;
- const f32 hf_gain = Pow10(info.room_hf / 2000.0f);
- if (hf_gain >= 1.0f) {
- state.lowpass_2 = 1.0f;
- state.lowpass_1 = 0.0f;
- } else {
- const auto a = 1.0f - hf_gain;
- const auto b = 2.0f * (2.0f - hf_gain * CosD(256.0f * info.hf_reference /
- static_cast<f32>(info.sample_rate)));
- const auto c = std::sqrt(b * b - 4.0f * a * a);
-
- state.lowpass_1 = (b - c) / (2.0f * a);
- state.lowpass_2 = 1.0f - state.lowpass_1;
- }
- state.early_to_late_taps = AudioCommon::CalculateDelaySamples(
- sample_rate, 1000.0f * (info.reflection_delay + info.reverb_delay));
-
- state.last_reverb_echo = 0.6f * info.diffusion * 0.01f;
- for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
- const auto length =
- FDN_MIN_DELAY_LINE_TIMES[i] +
- (info.density / 100.0f) * (FDN_MAX_DELAY_LINE_TIMES[i] - FDN_MIN_DELAY_LINE_TIMES[i]);
- state.fdn_delay_line[i].SetDelay(AudioCommon::CalculateDelaySamples(sample_rate, length));
-
- const auto delay_sample_counts = state.fdn_delay_line[i].GetDelay() +
- state.decay_delay_line0[i].GetDelay() +
- state.decay_delay_line1[i].GetDelay();
-
- float a = (-60.0f * static_cast<f32>(delay_sample_counts)) /
- (info.decay_time * static_cast<f32>(info.sample_rate));
- float b = a / info.hf_decay_ratio;
- float c = CosD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)) /
- SinD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate));
- float d = Pow10((b - a) / 40.0f);
- float e = Pow10((b + a) / 40.0f) * 0.7071f;
-
- state.lpf_coefficients[0][i] = e * ((d * c) + 1.0f) / (c + d);
- state.lpf_coefficients[1][i] = e * (1.0f - (d * c)) / (c + d);
- state.lpf_coefficients[2][i] = (c - d) / (c + d);
-
- state.decay_delay_line0[i].SetCoefficient(state.last_reverb_echo);
- state.decay_delay_line1[i].SetCoefficient(-0.9f * state.last_reverb_echo);
- }
-
- if (should_clear) {
- for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
- state.fdn_delay_line[i].Clear();
- state.decay_delay_line0[i].Clear();
- state.decay_delay_line1[i].Clear();
- }
- state.early_delay_line.Clear();
- state.center_delay_line.Clear();
- }
-
- const auto max_early_delay = state.early_delay_line.GetMaxDelay();
- const auto reflection_time = 1000.0f * (0.9998f * info.reverb_delay + 0.02f);
- for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) {
- const auto length = AudioCommon::CalculateDelaySamples(
- sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]);
- state.early_tap_steps[tap] = std::min(length, max_early_delay);
- }
-}
-
-void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
- s32 channel, s32 node_id) {
- const auto last = static_cast<s32>(last_volume * 32768.0f);
- const auto current = static_cast<s32>(current_volume * 32768.0f);
- const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) /
- static_cast<float>(worker_params.sample_count));
-
- if (dumping_frame) {
- LOG_DEBUG(Audio,
- "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, "
- "last_volume={}, current_volume={}",
- node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel),
- last_volume, current_volume);
- }
- // Apply generic gain on samples
- ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta,
- worker_params.sample_count);
-}
-
-void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
- const MixVolumeBuffer& last_mix_volumes,
- VoiceState& dsp_state, s32 mix_buffer_offset,
- s32 mix_buffer_count, s32 voice_index, s32 node_id) {
- // Loop all our mix buffers
- for (s32 i = 0; i < mix_buffer_count; i++) {
- if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) {
- const auto delta = static_cast<float>((mix_volumes[i] - last_mix_volumes[i])) /
- static_cast<float>(worker_params.sample_count);
-
- if (dumping_frame) {
- LOG_DEBUG(Audio,
- "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, "
- "output={}, last_volume={}, current_volume={}",
- node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i],
- mix_volumes[i]);
- }
-
- dsp_state.previous_samples[i] =
- ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index),
- last_mix_volumes[i], delta, worker_params.sample_count);
- } else {
- dsp_state.previous_samples[i] = 0;
- }
- }
-}
-
-void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) {
- if (dumping_frame) {
- LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand");
- }
- const auto& in_params = mix_info.GetInParams();
- GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
- in_params.sample_rate);
-
- GenerateEffectCommand(mix_info);
-
- GenerateMixCommands(mix_info);
-}
-
-void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) {
- if (!mix_info.HasAnyConnection()) {
- return;
- }
- const auto& in_params = mix_info.GetInParams();
- if (in_params.dest_mix_id != AudioCommon::NO_MIX) {
- const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id);
- const auto& dest_in_params = dest_mix.GetInParams();
-
- const auto buffer_count = in_params.buffer_count;
-
- for (s32 i = 0; i < buffer_count; i++) {
- for (s32 j = 0; j < dest_in_params.buffer_count; j++) {
- const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j];
- if (mixed_volume != 0.0f) {
- GenerateMixCommand(dest_in_params.buffer_offset + j,
- in_params.buffer_offset + i, mixed_volume,
- in_params.node_id);
- }
- }
- }
- } else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) {
- s32 base{};
- while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) {
- if (!destination_data->IsConfigured()) {
- continue;
- }
-
- const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId());
- const auto& dest_in_params = dest_mix.GetInParams();
- const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset;
- for (std::size_t i = 0; i < static_cast<std::size_t>(dest_in_params.buffer_count);
- i++) {
- const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i);
- if (mixed_volume != 0.0f) {
- GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume,
- in_params.node_id);
- }
- }
- }
- }
-}
-
-void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset,
- float volume, s32 node_id) {
-
- if (dumping_frame) {
- LOG_DEBUG(Audio,
- "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}",
- node_id, input_offset, output_offset, volume);
- }
-
- std::span<s32> output = GetMixBuffer(output_offset);
- std::span<const s32> input = GetMixBuffer(input_offset);
-
- const s32 gain = static_cast<s32>(volume * 32768.0f);
- // Mix with loop unrolling
- if (worker_params.sample_count % 4 == 0) {
- ApplyMix<4>(output, input, gain, worker_params.sample_count);
- } else if (worker_params.sample_count % 2 == 0) {
- ApplyMix<2>(output, input, gain, worker_params.sample_count);
- } else {
- ApplyMix<1>(output, input, gain, worker_params.sample_count);
- }
-}
-
-void CommandGenerator::GenerateFinalMixCommand() {
- if (dumping_frame) {
- LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand");
- }
- auto& mix_info = mix_context.GetFinalMixInfo();
- const auto& in_params = mix_info.GetInParams();
-
- GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
- in_params.sample_rate);
-
- GenerateEffectCommand(mix_info);
-
- for (s32 i = 0; i < in_params.buffer_count; i++) {
- const s32 gain = static_cast<s32>(in_params.volume * 32768.0f);
- if (dumping_frame) {
- LOG_DEBUG(
- Audio,
- "(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}",
- in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i,
- in_params.volume);
- }
- ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i),
- GetMixBuffer(in_params.buffer_offset + i), gain,
- worker_params.sample_count);
- }
-}
-
-template <typename T>
-s32 CommandGenerator::DecodePcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
- s32 sample_start_offset, s32 sample_end_offset, s32 sample_count,
- s32 channel, std::size_t mix_offset) {
- const auto& in_params = voice_info.GetInParams();
- const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
- if (wave_buffer.buffer_address == 0) {
- return 0;
- }
- if (wave_buffer.buffer_size == 0) {
- return 0;
- }
- if (sample_end_offset < sample_start_offset) {
- return 0;
- }
- const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset;
- const auto start_offset =
- ((dsp_state.offset + sample_start_offset) * in_params.channel_count) * sizeof(T);
- const auto buffer_pos = wave_buffer.buffer_address + start_offset;
- const auto samples_processed = std::min(sample_count, samples_remaining);
-
- const auto channel_count = in_params.channel_count;
- std::vector<T> buffer(samples_processed * channel_count);
- memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(T));
-
- if constexpr (std::is_floating_point_v<T>) {
- for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
- sample_buffer[mix_offset + i] = static_cast<s32>(buffer[i * channel_count + channel] *
- std::numeric_limits<s16>::max());
- }
- } else if constexpr (sizeof(T) == 1) {
- for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
- sample_buffer[mix_offset + i] =
- static_cast<s32>(static_cast<f32>(buffer[i * channel_count + channel] /
- std::numeric_limits<s8>::max()) *
- std::numeric_limits<s16>::max());
- }
- } else if constexpr (sizeof(T) == 2) {
- for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
- sample_buffer[mix_offset + i] = buffer[i * channel_count + channel];
- }
- } else {
- for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
- sample_buffer[mix_offset + i] =
- static_cast<s32>(static_cast<f32>(buffer[i * channel_count + channel] /
- std::numeric_limits<s32>::max()) *
- std::numeric_limits<s16>::max());
- }
- }
-
- return samples_processed;
-}
-
-s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
- s32 sample_start_offset, s32 sample_end_offset, s32 sample_count,
- [[maybe_unused]] s32 channel, std::size_t mix_offset) {
- const auto& in_params = voice_info.GetInParams();
- const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
- if (wave_buffer.buffer_address == 0) {
- return 0;
- }
- if (wave_buffer.buffer_size == 0) {
- return 0;
- }
- if (sample_end_offset < sample_start_offset) {
- return 0;
- }
-
- static constexpr std::array<int, 16> SIGNED_NIBBLES{
- 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
- };
-
- constexpr std::size_t FRAME_LEN = 8;
- constexpr std::size_t NIBBLES_PER_SAMPLE = 16;
- constexpr std::size_t SAMPLES_PER_FRAME = 14;
-
- auto frame_header = dsp_state.context.header;
- s32 idx = (frame_header >> 4) & 0xf;
- s32 scale = frame_header & 0xf;
- s16 yn1 = dsp_state.context.yn1;
- s16 yn2 = dsp_state.context.yn2;
-
- Codec::ADPCM_Coeff coeffs;
- memory.ReadBlock(in_params.additional_params_address, coeffs.data(),
- sizeof(Codec::ADPCM_Coeff));
-
- s32 coef1 = coeffs[idx * 2];
- s32 coef2 = coeffs[idx * 2 + 1];
-
- const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset;
- const auto samples_processed = std::min(sample_count, samples_remaining);
- const auto sample_pos = dsp_state.offset + sample_start_offset;
-
- const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME;
- auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) +
- samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0);
-
- const auto decode_sample = [&](const int nibble) -> s16 {
- const int xn = nibble * (1 << scale);
- // We first transform everything into 11 bit fixed point, perform the second order
- // digital filter, then transform back.
- // 0x400 == 0.5 in 11 bit fixed point.
- // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
- int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
- // Clamp to output range.
- val = std::clamp<s32>(val, -32768, 32767);
- // Advance output feedback.
- yn2 = yn1;
- yn1 = static_cast<s16>(val);
- return yn1;
- };
-
- std::size_t buffer_offset{};
- std::vector<u8> buffer(
- std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN));
- memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(),
- buffer.size());
- std::size_t cur_mix_offset = mix_offset;
-
- auto remaining_samples = samples_processed;
- while (remaining_samples > 0) {
- if (position_in_frame % NIBBLES_PER_SAMPLE == 0) {
- // Read header
- frame_header = buffer[buffer_offset++];
- idx = (frame_header >> 4) & 0xf;
- scale = frame_header & 0xf;
- coef1 = coeffs[idx * 2];
- coef2 = coeffs[idx * 2 + 1];
- position_in_frame += 2;
-
- // Decode entire frame
- if (remaining_samples >= static_cast<int>(SAMPLES_PER_FRAME)) {
- for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) {
- // Sample 1
- const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4];
- const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf];
- const s16 sample_1 = decode_sample(s0);
- const s16 sample_2 = decode_sample(s1);
- sample_buffer[cur_mix_offset++] = sample_1;
- sample_buffer[cur_mix_offset++] = sample_2;
- }
- remaining_samples -= static_cast<int>(SAMPLES_PER_FRAME);
- position_in_frame += SAMPLES_PER_FRAME;
- continue;
- }
- }
- // Decode mid frame
- s32 current_nibble = buffer[buffer_offset];
- if (position_in_frame++ & 0x1) {
- current_nibble &= 0xf;
- buffer_offset++;
- } else {
- current_nibble >>= 4;
- }
- const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]);
- sample_buffer[cur_mix_offset++] = sample;
- remaining_samples--;
- }
-
- dsp_state.context.header = frame_header;
- dsp_state.context.yn1 = yn1;
- dsp_state.context.yn2 = yn2;
-
- return samples_processed;
-}
-
-std::span<s32> CommandGenerator::GetMixBuffer(std::size_t index) {
- return std::span<s32>(mix_buffer.data() + (index * worker_params.sample_count),
- worker_params.sample_count);
-}
-
-std::span<const s32> CommandGenerator::GetMixBuffer(std::size_t index) const {
- return std::span<const s32>(mix_buffer.data() + (index * worker_params.sample_count),
- worker_params.sample_count);
-}
-
-std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const {
- return worker_params.mix_buffer_count + channel;
-}
-
-std::size_t CommandGenerator::GetTotalMixBufferCount() const {
- return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT;
-}
-
-std::span<s32> CommandGenerator::GetChannelMixBuffer(s32 channel) {
- return GetMixBuffer(worker_params.mix_buffer_count + channel);
-}
-
-std::span<const s32> CommandGenerator::GetChannelMixBuffer(s32 channel) const {
- return GetMixBuffer(worker_params.mix_buffer_count + channel);
-}
-
-void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span<s32> output,
- VoiceState& dsp_state, s32 channel,
- s32 target_sample_rate, s32 sample_count,
- s32 node_id) {
- const auto& in_params = voice_info.GetInParams();
- if (dumping_frame) {
- LOG_DEBUG(Audio,
- "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, "
- "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}",
- node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate,
- in_params.mix_id, in_params.splitter_info_id);
- }
- ASSERT_OR_EXECUTE(output.data() != nullptr, { return; });
-
- const auto resample_rate = static_cast<s32>(
- static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) *
- static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f)));
- if (dsp_state.fraction + sample_count * resample_rate >
- static_cast<s32>(SCALED_MIX_BUFFER_SIZE - 4ULL)) {
- return;
- }
-
- auto min_required_samples =
- std::min(static_cast<s32>(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate);
- if (min_required_samples >= sample_count) {
- min_required_samples = sample_count;
- }
-
- std::size_t temp_mix_offset{};
- s32 samples_output{};
- auto samples_remaining = sample_count;
- while (samples_remaining > 0) {
- const auto samples_to_output = std::min(samples_remaining, min_required_samples);
- const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15;
-
- if (!in_params.behavior_flags.is_pitch_and_src_skipped) {
- // Append sample histtory for resampler
- for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
- sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i];
- }
- temp_mix_offset += 4;
- }
-
- s32 samples_read{};
- while (samples_read < samples_to_read) {
- const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
- // No more data can be read
- if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) {
- break;
- }
-
- if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 &&
- wave_buffer.context_address != 0 && wave_buffer.context_size != 0) {
- memory.ReadBlock(wave_buffer.context_address, &dsp_state.context,
- sizeof(ADPCMContext));
- }
-
- s32 samples_offset_start;
- s32 samples_offset_end;
- if (dsp_state.loop_count > 0 && wave_buffer.loop_start_sample != 0 &&
- wave_buffer.loop_end_sample != 0 &&
- wave_buffer.loop_start_sample <= wave_buffer.loop_end_sample) {
- samples_offset_start = wave_buffer.loop_start_sample;
- samples_offset_end = wave_buffer.loop_end_sample;
- } else {
- samples_offset_start = wave_buffer.start_sample_offset;
- samples_offset_end = wave_buffer.end_sample_offset;
- }
-
- s32 samples_decoded{0};
- switch (in_params.sample_format) {
- case SampleFormat::Pcm8:
- samples_decoded =
- DecodePcm<s8>(voice_info, dsp_state, samples_offset_start, samples_offset_end,
- samples_to_read - samples_read, channel, temp_mix_offset);
- break;
- case SampleFormat::Pcm16:
- samples_decoded =
- DecodePcm<s16>(voice_info, dsp_state, samples_offset_start, samples_offset_end,
- samples_to_read - samples_read, channel, temp_mix_offset);
- break;
- case SampleFormat::Pcm32:
- samples_decoded =
- DecodePcm<s32>(voice_info, dsp_state, samples_offset_start, samples_offset_end,
- samples_to_read - samples_read, channel, temp_mix_offset);
- break;
- case SampleFormat::PcmFloat:
- samples_decoded =
- DecodePcm<f32>(voice_info, dsp_state, samples_offset_start, samples_offset_end,
- samples_to_read - samples_read, channel, temp_mix_offset);
- break;
- case SampleFormat::Adpcm:
- samples_decoded =
- DecodeAdpcm(voice_info, dsp_state, samples_offset_start, samples_offset_end,
- samples_to_read - samples_read, channel, temp_mix_offset);
- break;
- default:
- UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
- }
-
- temp_mix_offset += samples_decoded;
- samples_read += samples_decoded;
- dsp_state.offset += samples_decoded;
- dsp_state.played_sample_count += samples_decoded;
-
- if (dsp_state.offset >= (samples_offset_end - samples_offset_start) ||
- samples_decoded == 0) {
- // Reset our sample offset
- dsp_state.offset = 0;
- if (wave_buffer.is_looping) {
- dsp_state.loop_count++;
- if (wave_buffer.loop_count > 0 &&
- (dsp_state.loop_count > wave_buffer.loop_count || samples_decoded == 0)) {
- // End of our buffer
- voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer);
- }
-
- if (samples_decoded == 0) {
- break;
- }
-
- if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) {
- dsp_state.played_sample_count = 0;
- }
- } else {
- // Update our wave buffer states
- voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer);
- }
- }
- }
-
- if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) {
- // No need to resample
- std::memcpy(output.data() + samples_output, sample_buffer.data(),
- samples_read * sizeof(s32));
- } else {
- std::fill(sample_buffer.begin() + temp_mix_offset,
- sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read),
- 0);
- AudioCore::Resample(output.data() + samples_output, sample_buffer.data(), resample_rate,
- dsp_state.fraction, samples_to_output);
- // Resample
- for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
- dsp_state.sample_history[i] = sample_buffer[samples_to_read + i];
- }
- }
- samples_remaining -= samples_to_output;
- samples_output += samples_to_output;
- }
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h
deleted file mode 100644
index 59a33ba76..000000000
--- a/src/audio_core/command_generator.h
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <span>
-#include "audio_core/common.h"
-#include "audio_core/voice_context.h"
-#include "common/common_types.h"
-
-namespace Core::Memory {
-class Memory;
-}
-
-namespace AudioCore {
-class MixContext;
-class SplitterContext;
-class ServerSplitterDestinationData;
-class ServerMixInfo;
-class EffectContext;
-class EffectBase;
-struct AuxInfoDSP;
-struct I3dl2ReverbParams;
-struct I3dl2ReverbState;
-using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
-
-class CommandGenerator {
-public:
- explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_,
- VoiceContext& voice_context_, MixContext& mix_context_,
- SplitterContext& splitter_context_, EffectContext& effect_context_,
- Core::Memory::Memory& memory_);
- ~CommandGenerator();
-
- void ClearMixBuffers();
- void GenerateVoiceCommands();
- void GenerateVoiceCommand(ServerVoiceInfo& voice_info);
- void GenerateSubMixCommands();
- void GenerateFinalMixCommands();
- void PreCommand();
- void PostCommand();
-
- [[nodiscard]] std::span<s32> GetChannelMixBuffer(s32 channel);
- [[nodiscard]] std::span<const s32> GetChannelMixBuffer(s32 channel) const;
- [[nodiscard]] std::span<s32> GetMixBuffer(std::size_t index);
- [[nodiscard]] std::span<const s32> GetMixBuffer(std::size_t index) const;
- [[nodiscard]] std::size_t GetMixChannelBufferOffset(s32 channel) const;
-
- [[nodiscard]] std::size_t GetTotalMixBufferCount() const;
-
-private:
- void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel);
- void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
- s32 mix_buffer_count, s32 channel);
- void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel,
- s32 node_id);
- void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
- const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state,
- s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index,
- s32 node_id);
- void GenerateSubMixCommand(ServerMixInfo& mix_info);
- void GenerateMixCommands(ServerMixInfo& mix_info);
- void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume,
- s32 node_id);
- void GenerateFinalMixCommand();
- void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params,
- std::array<s64, 2>& state, std::size_t input_offset,
- std::size_t output_offset, s32 sample_count, s32 node_id);
- void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count,
- std::size_t mix_buffer_offset);
- void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
- std::size_t mix_buffer_offset, s32 sample_rate);
- void GenerateEffectCommand(ServerMixInfo& mix_info);
- void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
- void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
- void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
- [[nodiscard]] ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
-
- s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
- std::span<const s32> data, u32 sample_count, u32 write_offset,
- u32 write_count);
- s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
- std::span<s32> out_data, u32 sample_count, u32 read_offset, u32 read_count);
-
- void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
- std::vector<u8>& work_buffer);
- void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear);
- // DSP Code
- template <typename T>
- s32 DecodePcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset,
- s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset);
- s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset,
- s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset);
- void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span<s32> output,
- VoiceState& dsp_state, s32 channel, s32 target_sample_rate,
- s32 sample_count, s32 node_id);
-
- AudioCommon::AudioRendererParameter& worker_params;
- VoiceContext& voice_context;
- MixContext& mix_context;
- SplitterContext& splitter_context;
- EffectContext& effect_context;
- Core::Memory::Memory& memory;
- std::vector<s32> mix_buffer{};
- std::vector<s32> sample_buffer{};
- std::vector<s32> depop_buffer{};
- bool dumping_frame{false};
-};
-} // namespace AudioCore
diff --git a/src/audio_core/common.h b/src/audio_core/common.h
deleted file mode 100644
index 1ab537588..000000000
--- a/src/audio_core/common.h
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/swap.h"
-#include "core/hle/result.h"
-
-namespace AudioCommon {
-namespace Audren {
-constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
-constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};
-} // namespace Audren
-
-constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '9');
-constexpr std::size_t MAX_MIX_BUFFERS = 24;
-constexpr std::size_t MAX_BIQUAD_FILTERS = 2;
-constexpr std::size_t MAX_CHANNEL_COUNT = 6;
-constexpr std::size_t MAX_WAVE_BUFFERS = 4;
-constexpr std::size_t MAX_SAMPLE_HISTORY = 4;
-constexpr u32 STREAM_SAMPLE_RATE = 48000;
-constexpr u32 STREAM_NUM_CHANNELS = 2;
-constexpr s32 NO_SPLITTER = -1;
-constexpr s32 NO_MIX = 0x7fffffff;
-constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min();
-constexpr s32 FINAL_MIX = 0;
-constexpr s32 NO_EFFECT_ORDER = -1;
-constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant
-// Any size checks seem to take the sample history into account
-// and our const ends up being 0x3f04, the 4 bytes are most
-// likely the sample history
-constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY;
-constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f;
-constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f;
-constexpr std::size_t I3DL2REVERB_TAPS = 20;
-constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4;
-using Fractional = s32;
-
-template <typename T>
-constexpr Fractional ToFractional(T x) {
- return static_cast<Fractional>(x * static_cast<T>(0x4000));
-}
-
-constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) {
- return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14);
-}
-
-constexpr s32 FractionalToFixed(Fractional x) {
- const auto s = x & (1 << 13);
- return static_cast<s32>(x >> 14) + s;
-}
-
-constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) {
- return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time)));
-}
-
-static constexpr u32 VersionFromRevision(u32_le rev) {
- // "REV7" -> 7
- return ((rev >> 24) & 0xff) - 0x30;
-}
-
-static constexpr bool IsRevisionSupported(u32 required, u32_le user_revision) {
- const auto base = VersionFromRevision(user_revision);
- return required <= base;
-}
-
-static constexpr bool IsValidRevision(u32_le revision) {
- const auto base = VersionFromRevision(revision);
- constexpr auto max_rev = VersionFromRevision(CURRENT_PROCESS_REVISION);
- return base <= max_rev;
-}
-
-static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std::size_t required) {
- if (offset > size) {
- return false;
- }
- if (size < required) {
- return false;
- }
- if ((size - offset) < required) {
- return false;
- }
- return true;
-}
-
-struct UpdateDataSizes {
- u32_le behavior{};
- u32_le memory_pool{};
- u32_le voice{};
- u32_le voice_channel_resource{};
- u32_le effect{};
- u32_le mixer{};
- u32_le sink{};
- u32_le performance{};
- u32_le splitter{};
- u32_le render_info{};
- INSERT_PADDING_WORDS(4);
-};
-static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size");
-
-struct UpdateDataHeader {
- u32_le revision{};
- UpdateDataSizes size{};
- u32_le total_size{};
-};
-static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size");
-
-struct AudioRendererParameter {
- u32_le sample_rate;
- u32_le sample_count;
- u32_le mix_buffer_count;
- u32_le submix_count;
- u32_le voice_count;
- u32_le sink_count;
- u32_le effect_count;
- u32_le performance_frame_count;
- u8 is_voice_drop_enabled;
- u8 unknown_21;
- u8 unknown_22;
- u8 execution_mode;
- u32_le splitter_count;
- u32_le num_splitter_send_channels;
- u32_le unknown_30;
- u32_le revision;
-};
-static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
-
-} // namespace AudioCommon
diff --git a/src/audio_core/common/audio_renderer_parameter.h b/src/audio_core/common/audio_renderer_parameter.h
new file mode 100644
index 000000000..2f62c383b
--- /dev/null
+++ b/src/audio_core/common/audio_renderer_parameter.h
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "audio_core/renderer/upsampler/upsampler_manager.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+/**
+ * Execution mode of the audio renderer.
+ * Only Auto is currently supported.
+ */
+enum class ExecutionMode : u8 {
+ Auto,
+ Manual,
+};
+
+/**
+ * Parameters from the game, passed to the audio renderer for initialisation.
+ */
+struct AudioRendererParameterInternal {
+ /* 0x00 */ u32 sample_rate;
+ /* 0x04 */ u32 sample_count;
+ /* 0x08 */ u32 mixes;
+ /* 0x0C */ u32 sub_mixes;
+ /* 0x10 */ u32 voices;
+ /* 0x14 */ u32 sinks;
+ /* 0x18 */ u32 effects;
+ /* 0x1C */ u32 perf_frames;
+ /* 0x20 */ u16 voice_drop_enabled;
+ /* 0x22 */ u8 rendering_device;
+ /* 0x23 */ ExecutionMode execution_mode;
+ /* 0x24 */ u32 splitter_infos;
+ /* 0x28 */ s32 splitter_destinations;
+ /* 0x2C */ u32 external_context_size;
+ /* 0x30 */ u32 revision;
+ /* 0x34 */ char unk34[0x4];
+};
+static_assert(sizeof(AudioRendererParameterInternal) == 0x38,
+ "AudioRendererParameterInternal has the wrong size!");
+
+/**
+ * Context for rendering, contains a bunch of useful fields for the command generator.
+ */
+struct AudioRendererSystemContext {
+ s32 session_id;
+ s8 channels;
+ s16 mix_buffer_count;
+ AudioRenderer::BehaviorInfo* behavior;
+ std::span<s32> depop_buffer;
+ AudioRenderer::UpsamplerManager* upsampler_manager;
+ AudioRenderer::MemoryPoolInfo* memory_pool_info;
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/common/common.h b/src/audio_core/common/common.h
new file mode 100644
index 000000000..6abd9be45
--- /dev/null
+++ b/src/audio_core/common/common.h
@@ -0,0 +1,138 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <numeric>
+#include <span>
+
+#include "common/assert.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+using CpuAddr = std::uintptr_t;
+
+enum class PlayState : u8 {
+ Started,
+ Stopped,
+ Paused,
+};
+
+enum class SrcQuality : u8 {
+ Medium,
+ High,
+ Low,
+};
+
+enum class SampleFormat : u8 {
+ Invalid,
+ PcmInt8,
+ PcmInt16,
+ PcmInt24,
+ PcmInt32,
+ PcmFloat,
+ Adpcm,
+};
+
+enum class SessionTypes {
+ AudioIn,
+ AudioOut,
+ FinalOutputRecorder,
+};
+
+enum class Channels : u32 {
+ FrontLeft,
+ FrontRight,
+ Center,
+ LFE,
+ BackLeft,
+ BackRight,
+};
+
+// These are used by Delay, Reverb and I3dl2Reverb prior to Revision 11.
+enum class OldChannels : u32 {
+ FrontLeft,
+ FrontRight,
+ BackLeft,
+ BackRight,
+ Center,
+ LFE,
+};
+
+constexpr u32 BufferCount = 32;
+
+constexpr u32 MaxRendererSessions = 2;
+constexpr u32 TargetSampleCount = 240;
+constexpr u32 TargetSampleRate = 48'000;
+constexpr u32 MaxChannels = 6;
+constexpr u32 MaxMixBuffers = 24;
+constexpr u32 MaxWaveBuffers = 4;
+constexpr s32 LowestVoicePriority = 0xFF;
+constexpr s32 HighestVoicePriority = 0;
+constexpr u32 BufferAlignment = 0x40;
+constexpr u32 WorkbufferAlignment = 0x1000;
+constexpr s32 FinalMixId = 0;
+constexpr s32 InvalidDistanceFromFinalMix = std::numeric_limits<s32>::min();
+constexpr s32 UnusedSplitterId = -1;
+constexpr s32 UnusedMixId = std::numeric_limits<s32>::max();
+constexpr u32 InvalidNodeId = 0xF0000000;
+constexpr s32 InvalidProcessOrder = -1;
+constexpr u32 MaxBiquadFilters = 2;
+constexpr u32 MaxEffects = 256;
+
+constexpr bool IsChannelCountValid(u16 channel_count) {
+ return channel_count <= 6 &&
+ (channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6);
+}
+
+constexpr void UseOldChannelMapping(std::span<s16> inputs, std::span<s16> outputs) {
+ constexpr auto old_center{static_cast<u32>(OldChannels::Center)};
+ constexpr auto new_center{static_cast<u32>(Channels::Center)};
+ constexpr auto old_lfe{static_cast<u32>(OldChannels::LFE)};
+ constexpr auto new_lfe{static_cast<u32>(Channels::LFE)};
+
+ auto center{inputs[old_center]};
+ auto lfe{inputs[old_lfe]};
+ inputs[old_center] = inputs[new_center];
+ inputs[old_lfe] = inputs[new_lfe];
+ inputs[new_center] = center;
+ inputs[new_lfe] = lfe;
+
+ center = outputs[old_center];
+ lfe = outputs[old_lfe];
+ outputs[old_center] = outputs[new_center];
+ outputs[old_lfe] = outputs[new_lfe];
+ outputs[new_center] = center;
+ outputs[new_lfe] = lfe;
+}
+
+constexpr u32 GetSplitterInParamHeaderMagic() {
+ return Common::MakeMagic('S', 'N', 'D', 'H');
+}
+
+constexpr u32 GetSplitterInfoMagic() {
+ return Common::MakeMagic('S', 'N', 'D', 'I');
+}
+
+constexpr u32 GetSplitterSendDataMagic() {
+ return Common::MakeMagic('S', 'N', 'D', 'D');
+}
+
+constexpr size_t GetSampleFormatByteSize(SampleFormat format) {
+ switch (format) {
+ case SampleFormat::PcmInt8:
+ return 1;
+ case SampleFormat::PcmInt16:
+ return 2;
+ case SampleFormat::PcmInt24:
+ return 3;
+ case SampleFormat::PcmInt32:
+ case SampleFormat::PcmFloat:
+ return 4;
+ default:
+ return 2;
+ }
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/common/feature_support.h b/src/audio_core/common/feature_support.h
new file mode 100644
index 000000000..55c9e690d
--- /dev/null
+++ b/src/audio_core/common/feature_support.h
@@ -0,0 +1,105 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <map>
+#include <ranges>
+#include <tuple>
+
+#include "common/assert.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+constexpr u32 CurrentRevision = 11;
+
+enum class SupportTags {
+ CommandProcessingTimeEstimatorVersion4,
+ CommandProcessingTimeEstimatorVersion3,
+ CommandProcessingTimeEstimatorVersion2,
+ MultiTapBiquadFilterProcessing,
+ EffectInfoVer2,
+ WaveBufferVer2,
+ BiquadFilterFloatProcessing,
+ VolumeMixParameterPrecisionQ23,
+ MixInParameterDirtyOnlyUpdate,
+ BiquadFilterEffectStateClearBugFix,
+ VoicePlayedSampleCountResetAtLoopPoint,
+ VoicePitchAndSrcSkipped,
+ SplitterBugFix,
+ FlushVoiceWaveBuffers,
+ ElapsedFrameCount,
+ AudioRendererVariadicCommandBufferSize,
+ PerformanceMetricsDataFormatVersion2,
+ AudioRendererProcessingTimeLimit80Percent,
+ AudioRendererProcessingTimeLimit75Percent,
+ AudioRendererProcessingTimeLimit70Percent,
+ AdpcmLoopContextBugFix,
+ Splitter,
+ LongSizePreDelay,
+ AudioUsbDeviceOutput,
+ DeviceApiVersion2,
+ DelayChannelMappingChange,
+ ReverbChannelMappingChange,
+ I3dl2ReverbChannelMappingChange,
+
+ // Not a real tag, just here to get the count.
+ Size
+};
+
+constexpr u32 GetRevisionNum(u32 user_revision) {
+ if (user_revision >= 0x100) {
+ user_revision -= Common::MakeMagic('R', 'E', 'V', '0');
+ user_revision >>= 24;
+ }
+ return user_revision;
+};
+
+constexpr bool CheckFeatureSupported(SupportTags tag, u32 user_revision) {
+ constexpr std::array<std::pair<SupportTags, u32>, static_cast<u32>(SupportTags::Size)> features{
+ {
+ {SupportTags::AudioRendererProcessingTimeLimit70Percent, 1},
+ {SupportTags::Splitter, 2},
+ {SupportTags::AdpcmLoopContextBugFix, 2},
+ {SupportTags::LongSizePreDelay, 3},
+ {SupportTags::AudioUsbDeviceOutput, 4},
+ {SupportTags::AudioRendererProcessingTimeLimit75Percent, 4},
+ {SupportTags::VoicePlayedSampleCountResetAtLoopPoint, 5},
+ {SupportTags::VoicePitchAndSrcSkipped, 5},
+ {SupportTags::SplitterBugFix, 5},
+ {SupportTags::FlushVoiceWaveBuffers, 5},
+ {SupportTags::ElapsedFrameCount, 5},
+ {SupportTags::AudioRendererProcessingTimeLimit80Percent, 5},
+ {SupportTags::AudioRendererVariadicCommandBufferSize, 5},
+ {SupportTags::PerformanceMetricsDataFormatVersion2, 5},
+ {SupportTags::CommandProcessingTimeEstimatorVersion2, 5},
+ {SupportTags::BiquadFilterEffectStateClearBugFix, 6},
+ {SupportTags::BiquadFilterFloatProcessing, 7},
+ {SupportTags::VolumeMixParameterPrecisionQ23, 7},
+ {SupportTags::MixInParameterDirtyOnlyUpdate, 7},
+ {SupportTags::WaveBufferVer2, 8},
+ {SupportTags::CommandProcessingTimeEstimatorVersion3, 8},
+ {SupportTags::EffectInfoVer2, 9},
+ {SupportTags::CommandProcessingTimeEstimatorVersion4, 10},
+ {SupportTags::MultiTapBiquadFilterProcessing, 10},
+ {SupportTags::DelayChannelMappingChange, 11},
+ {SupportTags::ReverbChannelMappingChange, 11},
+ {SupportTags::I3dl2ReverbChannelMappingChange, 11},
+ }};
+
+ const auto& feature =
+ std::ranges::find_if(features, [tag](const auto& entry) { return entry.first == tag; });
+ if (feature == features.cend()) {
+ LOG_ERROR(Service_Audio, "Invalid SupportTag {}!", static_cast<u32>(tag));
+ return false;
+ }
+ user_revision = GetRevisionNum(user_revision);
+ return (*feature).second <= user_revision;
+}
+
+constexpr bool CheckValidRevision(u32 user_revision) {
+ return GetRevisionNum(user_revision) <= CurrentRevision;
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/common/wave_buffer.h b/src/audio_core/common/wave_buffer.h
new file mode 100644
index 000000000..fc478ef79
--- /dev/null
+++ b/src/audio_core/common/wave_buffer.h
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace AudioCore {
+
+struct WaveBufferVersion1 {
+ CpuAddr buffer;
+ u64 buffer_size;
+ u32 start_offset;
+ u32 end_offset;
+ bool loop;
+ bool stream_ended;
+ CpuAddr context;
+ u64 context_size;
+};
+
+struct WaveBufferVersion2 {
+ CpuAddr buffer;
+ CpuAddr context;
+ u64 buffer_size;
+ u64 context_size;
+ u32 start_offset;
+ u32 end_offset;
+ u32 loop_start_offset;
+ u32 loop_end_offset;
+ s32 loop_count;
+ bool loop;
+ bool stream_ended;
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/common/workbuffer_allocator.h b/src/audio_core/common/workbuffer_allocator.h
new file mode 100644
index 000000000..fb89f97fe
--- /dev/null
+++ b/src/audio_core/common/workbuffer_allocator.h
@@ -0,0 +1,100 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+/**
+ * Responsible for allocating up a workbuffer into multiple pieces.
+ * Takes in a buffer and size (it does not own them), and allocates up the buffer via Allocate.
+ */
+class WorkbufferAllocator {
+public:
+ explicit WorkbufferAllocator(std::span<u8> buffer_, u64 size_)
+ : buffer{reinterpret_cast<u64>(buffer_.data())}, size{size_} {}
+
+ /**
+ * Allocate the given count of T elements, aligned to alignment.
+ *
+ * @param count - The number of elements to allocate.
+ * @param alignment - The required starting alignment.
+ * @return Non-owning container of allocated elements.
+ */
+ template <typename T>
+ std::span<T> Allocate(u64 count, u64 alignment) {
+ u64 out{0};
+ u64 byte_size{count * sizeof(T)};
+
+ if (byte_size > 0) {
+ auto current{buffer + offset};
+ auto aligned_buffer{Common::AlignUp(current, alignment)};
+ if (aligned_buffer + byte_size <= buffer + size) {
+ out = aligned_buffer;
+ offset = byte_size - buffer + aligned_buffer;
+ } else {
+ LOG_ERROR(
+ Service_Audio,
+ "Allocated buffer was too small to hold new alloc.\nAllocator size={:08X}, "
+ "offset={:08X}.\nAttempting to allocate {:08X} with alignment={:02X}",
+ size, offset, byte_size, alignment);
+ count = 0;
+ }
+ }
+
+ return std::span<T>(reinterpret_cast<T*>(out), count);
+ }
+
+ /**
+ * Align the current offset to the given alignment.
+ *
+ * @param alignment - The required starting alignment.
+ */
+ void Align(u64 alignment) {
+ auto current{buffer + offset};
+ auto aligned_buffer{Common::AlignUp(current, alignment)};
+ offset = 0 - buffer + aligned_buffer;
+ }
+
+ /**
+ * Get the current buffer offset.
+ *
+ * @return The current allocating offset.
+ */
+ u64 GetCurrentOffset() const {
+ return offset;
+ }
+
+ /**
+ * Get the current buffer size.
+ *
+ * @return The size of the current buffer.
+ */
+ u64 GetSize() const {
+ return size;
+ }
+
+ /**
+ * Get the remaining size that can be allocated.
+ *
+ * @return The remaining size left in the buffer.
+ */
+ u64 GetRemainingSize() const {
+ return size - offset;
+ }
+
+private:
+ /// The buffer into which we are allocating.
+ u64 buffer;
+ /// Size of the buffer we're allocating to.
+ u64 size;
+ /// Current offset into the buffer, an error will be thrown if it exceeds size.
+ u64 offset{};
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
deleted file mode 100644
index 93c35e785..000000000
--- a/src/audio_core/cubeb_sink.cpp
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <atomic>
-#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"
-#include "common/settings.h"
-
-#ifdef _WIN32
-#include <objbase.h>
-#endif
-
-namespace AudioCore {
-
-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} {
-
- cubeb_stream_params params{};
- params.rate = sample_rate;
- params.channels = num_channels;
- params.format = CUBEB_SAMPLE_S16NE;
- params.prefs = CUBEB_STREAM_PREF_PERSIST;
- switch (num_channels) {
- case 1:
- params.layout = CUBEB_LAYOUT_MONO;
- break;
- case 2:
- params.layout = CUBEB_LAYOUT_STEREO;
- break;
- case 6:
- params.layout = CUBEB_LAYOUT_3F2_LFE;
- break;
- }
-
- u32 minimum_latency{};
- if (cubeb_get_min_latency(ctx, &params, &minimum_latency) != CUBEB_OK) {
- LOG_CRITICAL(Audio_Sink, "Error getting minimum latency");
- }
-
- if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device,
- &params, std::max(512u, minimum_latency),
- &CubebSinkStream::DataCallback, &CubebSinkStream::StateCallback,
- this) != CUBEB_OK) {
- LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream");
- return;
- }
-
- if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
- LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
- return;
- }
- }
-
- ~CubebSinkStream() override {
- if (!ctx) {
- return;
- }
-
- if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
- LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
- }
-
- cubeb_stream_destroy(stream_backend);
- }
-
- void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override {
- if (source_num_channels > num_channels) {
- // Downsample 6 channels to 2
- ASSERT_MSG(source_num_channels == 6, "Channel count must be 6");
-
- std::vector<s16> buf;
- buf.reserve(samples.size() * num_channels / source_num_channels);
- for (std::size_t i = 0; i < samples.size(); i += source_num_channels) {
- // Downmixing implementation taken from the ATSC standard
- const s16 left{samples[i + 0]};
- const s16 right{samples[i + 1]};
- const s16 center{samples[i + 2]};
- const s16 surround_left{samples[i + 4]};
- const s16 surround_right{samples[i + 5]};
- // Not used in the ATSC reference implementation
- [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]};
-
- constexpr s32 clev{707}; // center mixing level coefficient
- constexpr s32 slev{707}; // surround mixing level coefficient
-
- buf.push_back(static_cast<s16>(left + (clev * center / 1000) +
- (slev * surround_left / 1000)));
- buf.push_back(static_cast<s16>(right + (clev * center / 1000) +
- (slev * surround_right / 1000)));
- }
- queue.Push(buf);
- return;
- }
-
- queue.Push(samples);
- }
-
- std::size_t SamplesInQueue(u32 channel_count) const override {
- if (!ctx)
- return 0;
-
- return queue.Size() / channel_count;
- }
-
- void Flush() override {
- should_flush = true;
- }
-
- u32 GetNumChannels() const {
- return num_channels;
- }
-
-private:
- std::vector<std::string> device_list;
-
- cubeb* ctx{};
- cubeb_stream* stream_backend{};
- u32 num_channels{};
-
- 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);
- static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);
-};
-
-CubebSink::CubebSink(std::string_view target_device_name) {
- // Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
-#ifdef _WIN32
- com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
-#endif
-
- if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
- LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
- return;
- }
-
- if (target_device_name != auto_device_name && !target_device_name.empty()) {
- cubeb_device_collection collection;
- if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
- LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
- } else {
- const auto collection_end{collection.device + collection.count};
- const auto device{
- std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
- return info.friendly_name != nullptr &&
- target_device_name == info.friendly_name;
- })};
- if (device != collection_end) {
- output_device = device->devid;
- }
- cubeb_device_collection_destroy(ctx, &collection);
- }
- }
-}
-
-CubebSink::~CubebSink() {
- if (!ctx) {
- return;
- }
-
- for (auto& sink_stream : sink_streams) {
- sink_stream.reset();
- }
-
- cubeb_destroy(ctx);
-
-#ifdef _WIN32
- if (SUCCEEDED(com_init_result)) {
- CoUninitialize();
- }
-#endif
-}
-
-SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels,
- const std::string& name) {
- sink_streams.push_back(
- std::make_unique<CubebSinkStream>(ctx, sample_rate, num_channels, output_device, name));
- return *sink_streams.back();
-}
-
-long CubebSinkStream::DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data,
- [[maybe_unused]] const void* input_buffer, void* output_buffer,
- long num_frames) {
- auto* impl = static_cast<CubebSinkStream*>(user_data);
- auto* buffer = static_cast<u8*>(output_buffer);
-
- if (!impl) {
- return {};
- }
-
- 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);
-
- if (samples_written >= num_channels) {
- std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16),
- num_channels * sizeof(s16));
- }
-
- // Fill the rest of the frames with last_frame
- for (std::size_t i = samples_written; i < samples_to_write; i += num_channels) {
- std::memcpy(buffer + i * sizeof(s16), &impl->last_frame[0], num_channels * sizeof(s16));
- }
-
- return num_frames;
-}
-
-void CubebSinkStream::StateCallback([[maybe_unused]] cubeb_stream* stream,
- [[maybe_unused]] void* user_data,
- [[maybe_unused]] cubeb_state state) {}
-
-std::vector<std::string> ListCubebSinkDevices() {
- std::vector<std::string> device_list;
- cubeb* ctx;
-
- if (cubeb_init(&ctx, "yuzu Device Enumerator", nullptr) != CUBEB_OK) {
- LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
- return {};
- }
-
- cubeb_device_collection collection;
- if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
- LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
- } else {
- for (std::size_t i = 0; i < collection.count; i++) {
- const cubeb_device_info& device = collection.device[i];
- if (device.friendly_name) {
- device_list.emplace_back(device.friendly_name);
- }
- }
- cubeb_device_collection_destroy(ctx, &collection);
- }
-
- cubeb_destroy(ctx);
- return device_list;
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h
deleted file mode 100644
index 7ce850f47..000000000
--- a/src/audio_core/cubeb_sink.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <string>
-#include <vector>
-
-#include <cubeb/cubeb.h>
-
-#include "audio_core/sink.h"
-
-namespace AudioCore {
-
-class CubebSink final : public Sink {
-public:
- explicit CubebSink(std::string_view device_id);
- ~CubebSink() override;
-
- SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,
- const std::string& name) override;
-
-private:
- cubeb* ctx{};
- cubeb_devid output_device{};
- std::vector<SinkStreamPtr> sink_streams;
-
-#ifdef _WIN32
- u32 com_init_result = 0;
-#endif
-};
-
-std::vector<std::string> ListCubebSinkDevices();
-
-} // namespace AudioCore
diff --git a/src/audio_core/delay_line.cpp b/src/audio_core/delay_line.cpp
deleted file mode 100644
index 2793ed8db..000000000
--- a/src/audio_core/delay_line.cpp
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <cstring>
-#include "audio_core/delay_line.h"
-
-namespace AudioCore {
-DelayLineBase::DelayLineBase() = default;
-DelayLineBase::~DelayLineBase() = default;
-
-void DelayLineBase::Initialize(s32 max_delay_, float* src_buffer) {
- buffer = src_buffer;
- buffer_end = buffer + max_delay_;
- max_delay = max_delay_;
- output = buffer;
- SetDelay(max_delay_);
- Clear();
-}
-
-void DelayLineBase::SetDelay(s32 new_delay) {
- if (max_delay < new_delay) {
- return;
- }
- delay = new_delay;
- input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1));
-}
-
-s32 DelayLineBase::GetDelay() const {
- return delay;
-}
-
-s32 DelayLineBase::GetMaxDelay() const {
- return max_delay;
-}
-
-f32 DelayLineBase::TapOut(s32 last_sample) {
- const float* ptr = input - (last_sample + 1);
- if (ptr < buffer) {
- ptr += (max_delay + 1);
- }
-
- return *ptr;
-}
-
-f32 DelayLineBase::Tick(f32 sample) {
- *(input++) = sample;
- const auto out_sample = *(output++);
-
- if (buffer_end < input) {
- input = buffer;
- }
-
- if (buffer_end < output) {
- output = buffer;
- }
-
- return out_sample;
-}
-
-float* DelayLineBase::GetInput() {
- return input;
-}
-
-const float* DelayLineBase::GetInput() const {
- return input;
-}
-
-f32 DelayLineBase::GetOutputSample() const {
- return *output;
-}
-
-void DelayLineBase::Clear() {
- std::memset(buffer, 0, sizeof(float) * max_delay);
-}
-
-void DelayLineBase::Reset() {
- buffer = nullptr;
- buffer_end = nullptr;
- max_delay = 0;
- input = nullptr;
- output = nullptr;
- delay = 0;
-}
-
-DelayLineAllPass::DelayLineAllPass() = default;
-DelayLineAllPass::~DelayLineAllPass() = default;
-
-void DelayLineAllPass::Initialize(u32 delay_, float coeffcient_, f32* src_buffer) {
- DelayLineBase::Initialize(delay_, src_buffer);
- SetCoefficient(coeffcient_);
-}
-
-void DelayLineAllPass::SetCoefficient(float coeffcient_) {
- coefficient = coeffcient_;
-}
-
-f32 DelayLineAllPass::Tick(f32 sample) {
- const auto temp = sample - coefficient * *output;
- return coefficient * temp + DelayLineBase::Tick(temp);
-}
-
-void DelayLineAllPass::Reset() {
- coefficient = 0.0f;
- DelayLineBase::Reset();
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/delay_line.h b/src/audio_core/delay_line.h
deleted file mode 100644
index 84f11bc52..000000000
--- a/src/audio_core/delay_line.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// 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"
-
-namespace AudioCore {
-
-class DelayLineBase {
-public:
- DelayLineBase();
- ~DelayLineBase();
-
- void Initialize(s32 max_delay_, float* src_buffer);
- void SetDelay(s32 new_delay);
- s32 GetDelay() const;
- s32 GetMaxDelay() const;
- f32 TapOut(s32 last_sample);
- f32 Tick(f32 sample);
- float* GetInput();
- const float* GetInput() const;
- f32 GetOutputSample() const;
- void Clear();
- void Reset();
-
-protected:
- float* buffer{nullptr};
- float* buffer_end{nullptr};
- s32 max_delay{};
- float* input{nullptr};
- float* output{nullptr};
- s32 delay{};
-};
-
-class DelayLineAllPass final : public DelayLineBase {
-public:
- DelayLineAllPass();
- ~DelayLineAllPass();
-
- void Initialize(u32 delay, float coeffcient_, f32* src_buffer);
- void SetCoefficient(float coeffcient_);
- f32 Tick(f32 sample);
- void Reset();
-
-private:
- float coefficient{};
-};
-} // namespace AudioCore
diff --git a/src/audio_core/device/audio_buffer.h b/src/audio_core/device/audio_buffer.h
new file mode 100644
index 000000000..7128ef72a
--- /dev/null
+++ b/src/audio_core/device/audio_buffer.h
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace AudioCore {
+
+struct AudioBuffer {
+ /// Timestamp this buffer started playing.
+ u64 start_timestamp;
+ /// Timestamp this buffer should finish playing.
+ u64 end_timestamp;
+ /// Timestamp this buffer completed playing.
+ s64 played_timestamp;
+ /// Game memory address for these samples.
+ VAddr samples;
+ /// Unqiue identifier for this buffer.
+ u64 tag;
+ /// Size of the samples buffer.
+ u64 size;
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h
new file mode 100644
index 000000000..3dae1a3b7
--- /dev/null
+++ b/src/audio_core/device/audio_buffers.h
@@ -0,0 +1,317 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <mutex>
+#include <span>
+#include <vector>
+
+#include "audio_buffer.h"
+#include "audio_core/device/device_session.h"
+#include "core/core_timing.h"
+
+namespace AudioCore {
+
+constexpr s32 BufferAppendLimit = 4;
+
+/**
+ * A ringbuffer of N audio buffers.
+ * The buffer contains 3 sections:
+ * Appended - Buffers added to the ring, but have yet to be sent to the audio backend.
+ * Registered - Buffers sent to the backend and queued for playback.
+ * Released - Buffers which have been played, and can now be recycled.
+ * Any others are free/untracked.
+ *
+ * @tparam N - Maximum number of buffers in the ring.
+ */
+template <size_t N>
+class AudioBuffers {
+public:
+ explicit AudioBuffers(size_t limit) : append_limit{static_cast<u32>(limit)} {}
+
+ /**
+ * Append a new audio buffer to the ring.
+ *
+ * @param buffer - The new buffer.
+ */
+ void AppendBuffer(const AudioBuffer& buffer) {
+ std::scoped_lock l{lock};
+ buffers[appended_index] = buffer;
+ appended_count++;
+ appended_index = (appended_index + 1) % append_limit;
+ }
+
+ /**
+ * Register waiting buffers, up to a maximum of BufferAppendLimit.
+ *
+ * @param out_buffers - The buffers which were registered.
+ */
+ void RegisterBuffers(std::vector<AudioBuffer>& out_buffers) {
+ std::scoped_lock l{lock};
+ const s32 to_register{std::min(std::min(appended_count, BufferAppendLimit),
+ BufferAppendLimit - registered_count)};
+
+ for (s32 i = 0; i < to_register; i++) {
+ s32 index{appended_index - appended_count};
+ if (index < 0) {
+ index += N;
+ }
+
+ out_buffers.push_back(buffers[index]);
+ registered_count++;
+ registered_index = (registered_index + 1) % append_limit;
+
+ appended_count--;
+ if (appended_count == 0) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Release a single buffer. Must be already registered.
+ *
+ * @param index - The buffer index to release.
+ * @param timestamp - The released timestamp for this buffer.
+ */
+ void ReleaseBuffer(s32 index, s64 timestamp) {
+ std::scoped_lock l{lock};
+ buffers[index].played_timestamp = timestamp;
+
+ registered_count--;
+ released_count++;
+ released_index = (released_index + 1) % append_limit;
+ }
+
+ /**
+ * Release all registered buffers.
+ *
+ * @param core_timing - The CoreTiming instance
+ * @param session - The device session
+ *
+ * @return Is the buffer was released.
+ */
+ bool ReleaseBuffers(const Core::Timing::CoreTiming& core_timing, const DeviceSession& session) {
+ std::scoped_lock l{lock};
+ bool buffer_released{false};
+ while (registered_count > 0) {
+ auto index{registered_index - registered_count};
+ if (index < 0) {
+ index += N;
+ }
+
+ // Check with the backend if this buffer can be released yet.
+ if (!session.IsBufferConsumed(buffers[index])) {
+ break;
+ }
+
+ ReleaseBuffer(index, core_timing.GetGlobalTimeNs().count());
+ buffer_released = true;
+ }
+
+ return buffer_released || registered_count == 0;
+ }
+
+ /**
+ * Get all released buffers.
+ *
+ * @param tags - Container to be filled with the released buffers' tags.
+ * @return The number of buffers released.
+ */
+ u32 GetReleasedBuffers(std::span<u64> tags) {
+ std::scoped_lock l{lock};
+ u32 released{0};
+
+ while (released_count > 0) {
+ auto index{released_index - released_count};
+ if (index < 0) {
+ index += N;
+ }
+
+ auto& buffer{buffers[index]};
+ released_count--;
+
+ auto tag{buffer.tag};
+ buffer.played_timestamp = 0;
+ buffer.samples = 0;
+ buffer.tag = 0;
+ buffer.size = 0;
+
+ if (tag == 0) {
+ break;
+ }
+
+ tags[released++] = tag;
+
+ if (released >= tags.size()) {
+ break;
+ }
+ }
+
+ return released;
+ }
+
+ /**
+ * Get all appended and registered buffers.
+ *
+ * @param buffers_flushed - Output vector for the buffers which are released.
+ * @param max_buffers - Maximum number of buffers to released.
+ * @return The number of buffers released.
+ */
+ u32 GetRegisteredAppendedBuffers(std::vector<AudioBuffer>& buffers_flushed, u32 max_buffers) {
+ std::scoped_lock l{lock};
+ if (registered_count + appended_count == 0) {
+ return 0;
+ }
+
+ size_t buffers_to_flush{
+ std::min(static_cast<u32>(registered_count + appended_count), max_buffers)};
+ if (buffers_to_flush == 0) {
+ return 0;
+ }
+
+ while (registered_count > 0) {
+ auto index{registered_index - registered_count};
+ if (index < 0) {
+ index += N;
+ }
+
+ buffers_flushed.push_back(buffers[index]);
+
+ registered_count--;
+ released_count++;
+ released_index = (released_index + 1) % append_limit;
+
+ if (buffers_flushed.size() >= buffers_to_flush) {
+ break;
+ }
+ }
+
+ while (appended_count > 0) {
+ auto index{appended_index - appended_count};
+ if (index < 0) {
+ index += N;
+ }
+
+ buffers_flushed.push_back(buffers[index]);
+
+ appended_count--;
+ released_count++;
+ released_index = (released_index + 1) % append_limit;
+
+ if (buffers_flushed.size() >= buffers_to_flush) {
+ break;
+ }
+ }
+
+ return static_cast<u32>(buffers_flushed.size());
+ }
+
+ /**
+ * Check if the given tag is in the buffers.
+ *
+ * @param tag - Unique tag of the buffer to search for.
+ * @return True if the buffer is still in the ring, otherwise false.
+ */
+ bool ContainsBuffer(const u64 tag) const {
+ std::scoped_lock l{lock};
+ const auto registered_buffers{appended_count + registered_count + released_count};
+
+ if (registered_buffers == 0) {
+ return false;
+ }
+
+ auto index{released_index - released_count};
+ if (index < 0) {
+ index += append_limit;
+ }
+
+ for (s32 i = 0; i < registered_buffers; i++) {
+ if (buffers[index].tag == tag) {
+ return true;
+ }
+ index = (index + 1) % append_limit;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the number of active buffers in the ring.
+ * That is, appended, registered and released buffers.
+ *
+ * @return Number of active buffers.
+ */
+ u32 GetAppendedRegisteredCount() const {
+ std::scoped_lock l{lock};
+ return appended_count + registered_count;
+ }
+
+ /**
+ * Get the total number of active buffers in the ring.
+ * That is, appended, registered and released buffers.
+ *
+ * @return Number of active buffers.
+ */
+ u32 GetTotalBufferCount() const {
+ std::scoped_lock l{lock};
+ return static_cast<u32>(appended_count + registered_count + released_count);
+ }
+
+ /**
+ * Flush all of the currently appended and registered buffers
+ *
+ * @param buffers_released - Output count for the number of buffers released.
+ * @return True if buffers were successfully flushed, otherwise false.
+ */
+ bool FlushBuffers(u32& buffers_released) {
+ std::scoped_lock l{lock};
+ std::vector<AudioBuffer> buffers_flushed{};
+
+ buffers_released = GetRegisteredAppendedBuffers(buffers_flushed, append_limit);
+
+ if (registered_count > 0) {
+ return false;
+ }
+
+ if (static_cast<u32>(released_count + appended_count) > append_limit) {
+ return false;
+ }
+
+ return true;
+ }
+
+ u64 GetNextTimestamp() const {
+ // Iterate backwards through the buffer queue, and take the most recent buffer's end
+ std::scoped_lock l{lock};
+ auto index{appended_index - 1};
+ if (index < 0) {
+ index += append_limit;
+ }
+ return buffers[index].end_timestamp;
+ }
+
+private:
+ /// Buffer lock
+ mutable std::recursive_mutex lock{};
+ /// The audio buffers
+ std::array<AudioBuffer, N> buffers{};
+ /// Current released index
+ s32 released_index{};
+ /// Number of released buffers
+ s32 released_count{};
+ /// Current registered index
+ s32 registered_index{};
+ /// Number of registered buffers
+ s32 registered_count{};
+ /// Current appended index
+ s32 appended_index{};
+ /// Number of appended buffers
+ s32 appended_count{};
+ /// Maximum number of buffers (default 32)
+ u32 append_limit{};
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp
new file mode 100644
index 000000000..995060414
--- /dev/null
+++ b/src/audio_core/device/device_session.cpp
@@ -0,0 +1,132 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/audio_core.h"
+#include "audio_core/audio_manager.h"
+#include "audio_core/device/audio_buffer.h"
+#include "audio_core/device/device_session.h"
+#include "audio_core/sink/sink_stream.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/memory.h"
+
+namespace AudioCore {
+
+using namespace std::literals;
+constexpr auto INCREMENT_TIME{5ms};
+
+DeviceSession::DeviceSession(Core::System& system_)
+ : system{system_}, thread_event{Core::Timing::CreateEvent(
+ "AudioOutSampleTick",
+ [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
+ return ThreadFunc();
+ })} {}
+
+DeviceSession::~DeviceSession() {
+ Finalize();
+}
+
+Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_format_,
+ u16 channel_count_, size_t session_id_, u32 handle_,
+ u64 applet_resource_user_id_, Sink::StreamType type_) {
+ if (stream) {
+ Finalize();
+ }
+ name = fmt::format("{}-{}", name_, session_id_);
+ type = type_;
+ sample_format = sample_format_;
+ channel_count = channel_count_;
+ session_id = session_id_;
+ handle = handle_;
+ applet_resource_user_id = applet_resource_user_id_;
+
+ if (type == Sink::StreamType::In) {
+ sink = &system.AudioCore().GetInputSink();
+ } else {
+ sink = &system.AudioCore().GetOutputSink();
+ }
+ stream = sink->AcquireSinkStream(system, channel_count, name, type);
+ initialized = true;
+ return ResultSuccess;
+}
+
+void DeviceSession::Finalize() {
+ if (initialized) {
+ Stop();
+ sink->CloseStream(stream);
+ stream = nullptr;
+ }
+}
+
+void DeviceSession::Start() {
+ if (stream) {
+ stream->Start();
+ system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds::zero(), INCREMENT_TIME,
+ thread_event);
+ }
+}
+
+void DeviceSession::Stop() {
+ if (stream) {
+ stream->Stop();
+ system.CoreTiming().UnscheduleEvent(thread_event, {});
+ }
+}
+
+void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
+ for (const auto& buffer : buffers) {
+ Sink::SinkBuffer new_buffer{
+ .frames = buffer.size / (channel_count * sizeof(s16)),
+ .frames_played = 0,
+ .tag = buffer.tag,
+ .consumed = false,
+ };
+
+ if (type == Sink::StreamType::In) {
+ std::vector<s16> samples{};
+ stream->AppendBuffer(new_buffer, samples);
+ } else {
+ std::vector<s16> samples(buffer.size / sizeof(s16));
+ system.Memory().ReadBlockUnsafe(buffer.samples, samples.data(), buffer.size);
+ stream->AppendBuffer(new_buffer, samples);
+ }
+ }
+}
+
+void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const {
+ if (type == Sink::StreamType::In) {
+ auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
+ system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
+ }
+}
+
+bool DeviceSession::IsBufferConsumed(const AudioBuffer& buffer) const {
+ return played_sample_count >= buffer.end_timestamp;
+}
+
+void DeviceSession::SetVolume(f32 volume) const {
+ if (stream) {
+ stream->SetSystemVolume(volume);
+ }
+}
+
+u64 DeviceSession::GetPlayedSampleCount() const {
+ return played_sample_count;
+}
+
+std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() {
+ // Add 5ms of samples at a 48K sample rate.
+ played_sample_count += 48'000 * INCREMENT_TIME / 1s;
+ if (type == Sink::StreamType::Out) {
+ system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true);
+ } else {
+ system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioInManager, true);
+ }
+ return std::nullopt;
+}
+
+void DeviceSession::SetRingSize(u32 ring_size) {
+ stream->SetRingSize(ring_size);
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h
new file mode 100644
index 000000000..74f4dc085
--- /dev/null
+++ b/src/audio_core/device/device_session.h
@@ -0,0 +1,148 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+#include <optional>
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "audio_core/sink/sink.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace Core {
+class System;
+namespace Timing {
+struct EventType;
+} // namespace Timing
+} // namespace Core
+
+namespace AudioCore {
+
+namespace Sink {
+class SinkStream;
+struct SinkBuffer;
+} // namespace Sink
+
+struct AudioBuffer;
+
+/**
+ * Represents an input or output device stream for audio in and audio out (not used for render).
+ **/
+class DeviceSession {
+public:
+ explicit DeviceSession(Core::System& system);
+ ~DeviceSession();
+
+ /**
+ * Initialize this device session.
+ *
+ * @param name - Name of this device.
+ * @param sample_format - Sample format for this device's output.
+ * @param channel_count - Number of channels for this device (2 or 6).
+ * @param session_id - This session's id.
+ * @param handle - Handle for this device session (unused).
+ * @param applet_resource_user_id - Applet resource user id for this device session (unused).
+ * @param type - Type of this stream (Render, In, Out).
+ * @return Result code for this call.
+ */
+ Result Initialize(std::string_view name, SampleFormat sample_format, u16 channel_count,
+ size_t session_id, u32 handle, u64 applet_resource_user_id,
+ Sink::StreamType type);
+
+ /**
+ * Finalize this device session.
+ */
+ void Finalize();
+
+ /**
+ * Append audio buffers to this device session to be played back.
+ *
+ * @param buffers - The buffers to play.
+ */
+ void AppendBuffers(std::span<const AudioBuffer> buffers) const;
+
+ /**
+ * (Audio In only) Pop samples from the backend, and write them back to this buffer's address.
+ *
+ * @param buffer - The buffer to write to.
+ */
+ void ReleaseBuffer(const AudioBuffer& buffer) const;
+
+ /**
+ * Check if the buffer for the given tag has been consumed by the backend.
+ *
+ * @param buffer - the buffer to check.
+ *
+ * @return true if the buffer has been consumed, otherwise false.
+ */
+ bool IsBufferConsumed(const AudioBuffer& buffer) const;
+
+ /**
+ * Start this device session, starting the backend stream.
+ */
+ void Start();
+
+ /**
+ * Stop this device session, stopping the backend stream.
+ */
+ void Stop();
+
+ /**
+ * Set this device session's volume.
+ *
+ * @param volume - New volume for this session.
+ */
+ void SetVolume(f32 volume) const;
+
+ /**
+ * Get this device session's total played sample count.
+ *
+ * @return Samples played by this session.
+ */
+ u64 GetPlayedSampleCount() const;
+
+ /*
+ * CoreTiming callback to increment played_sample_count over time.
+ */
+ std::optional<std::chrono::nanoseconds> ThreadFunc();
+
+ /*
+ * Set the size of the ring buffer.
+ */
+ void SetRingSize(u32 ring_size);
+
+private:
+ /// System
+ Core::System& system;
+ /// Output sink this device will use
+ Sink::Sink* sink{};
+ /// The backend stream for this device session to send samples to
+ Sink::SinkStream* stream{};
+ /// Name of this device session
+ std::string name{};
+ /// Type of this device session (render/in/out)
+ Sink::StreamType type{};
+ /// Sample format for this device.
+ SampleFormat sample_format{SampleFormat::PcmInt16};
+ /// Channel count for this device session
+ u16 channel_count{};
+ /// Session id of this device session
+ size_t session_id{};
+ /// Handle of this device session
+ u32 handle{};
+ /// Applet resource user id of this device session
+ u64 applet_resource_user_id{};
+ /// Total number of samples played by this device session
+ std::atomic<u64> played_sample_count{};
+ /// Event increasing the played sample count every 5ms
+ std::shared_ptr<Core::Timing::EventType> thread_event;
+ /// Is this session initialised?
+ bool initialized{};
+ /// Buffer queue
+ std::vector<AudioBuffer> buffer_queue{};
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp
deleted file mode 100644
index 89e4573c7..000000000
--- a/src/audio_core/effect_context.cpp
+++ /dev/null
@@ -1,321 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include "audio_core/effect_context.h"
-
-namespace AudioCore {
-namespace {
-bool ValidChannelCountForEffect(s32 channel_count) {
- return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6;
-}
-} // namespace
-
-EffectContext::EffectContext(std::size_t effect_count_) : effect_count(effect_count_) {
- effects.reserve(effect_count);
- std::generate_n(std::back_inserter(effects), effect_count,
- [] { return std::make_unique<EffectStubbed>(); });
-}
-EffectContext::~EffectContext() = default;
-
-std::size_t EffectContext::GetCount() const {
- return effect_count;
-}
-
-EffectBase* EffectContext::GetInfo(std::size_t i) {
- return effects.at(i).get();
-}
-
-EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) {
- switch (effect) {
- case EffectType::Invalid:
- effects[i] = std::make_unique<EffectStubbed>();
- break;
- case EffectType::BufferMixer:
- effects[i] = std::make_unique<EffectBufferMixer>();
- break;
- case EffectType::Aux:
- effects[i] = std::make_unique<EffectAuxInfo>();
- break;
- case EffectType::Delay:
- effects[i] = std::make_unique<EffectDelay>();
- break;
- case EffectType::Reverb:
- effects[i] = std::make_unique<EffectReverb>();
- break;
- case EffectType::I3dl2Reverb:
- effects[i] = std::make_unique<EffectI3dl2Reverb>();
- break;
- case EffectType::BiquadFilter:
- effects[i] = std::make_unique<EffectBiquadFilter>();
- break;
- default:
- UNREACHABLE_MSG("Unimplemented effect {}", effect);
- effects[i] = std::make_unique<EffectStubbed>();
- }
- return GetInfo(i);
-}
-
-const EffectBase* EffectContext::GetInfo(std::size_t i) const {
- return effects.at(i).get();
-}
-
-EffectStubbed::EffectStubbed() : EffectBase(EffectType::Invalid) {}
-EffectStubbed::~EffectStubbed() = default;
-
-void EffectStubbed::Update([[maybe_unused]] EffectInfo::InParams& in_params) {}
-void EffectStubbed::UpdateForCommandGeneration() {}
-
-EffectBase::EffectBase(EffectType effect_type_) : effect_type(effect_type_) {}
-EffectBase::~EffectBase() = default;
-
-UsageState EffectBase::GetUsage() const {
- return usage;
-}
-
-EffectType EffectBase::GetType() const {
- return effect_type;
-}
-
-bool EffectBase::IsEnabled() const {
- return enabled;
-}
-
-s32 EffectBase::GetMixID() const {
- return mix_id;
-}
-
-s32 EffectBase::GetProcessingOrder() const {
- return processing_order;
-}
-
-std::vector<u8>& EffectBase::GetWorkBuffer() {
- return work_buffer;
-}
-
-const std::vector<u8>& EffectBase::GetWorkBuffer() const {
- return work_buffer;
-}
-
-EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {}
-EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
-
-void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
- auto& params = GetParams();
- const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data());
- if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
- UNREACHABLE_MSG("Invalid reverb max channel count {}", reverb_params->max_channels);
- return;
- }
-
- const auto last_status = params.status;
- mix_id = in_params.mix_id;
- processing_order = in_params.processing_order;
- params = *reverb_params;
- if (!ValidChannelCountForEffect(reverb_params->channel_count)) {
- params.channel_count = params.max_channels;
- }
- enabled = in_params.is_enabled;
- if (last_status != ParameterStatus::Updated) {
- params.status = last_status;
- }
-
- if (in_params.is_new || skipped) {
- usage = UsageState::Initialized;
- params.status = ParameterStatus::Initialized;
- skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
- if (!skipped) {
- auto& cur_work_buffer = GetWorkBuffer();
- // Has two buffers internally
- cur_work_buffer.resize(in_params.buffer_size * 2);
- std::fill(cur_work_buffer.begin(), cur_work_buffer.end(), 0);
- }
- }
-}
-
-void EffectI3dl2Reverb::UpdateForCommandGeneration() {
- if (enabled) {
- usage = UsageState::Running;
- } else {
- usage = UsageState::Stopped;
- }
- GetParams().status = ParameterStatus::Updated;
-}
-
-I3dl2ReverbState& EffectI3dl2Reverb::GetState() {
- return state;
-}
-
-const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const {
- return state;
-}
-
-EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {}
-EffectBiquadFilter::~EffectBiquadFilter() = default;
-
-void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) {
- auto& params = GetParams();
- const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data());
- mix_id = in_params.mix_id;
- processing_order = in_params.processing_order;
- params = *biquad_params;
- enabled = in_params.is_enabled;
-}
-
-void EffectBiquadFilter::UpdateForCommandGeneration() {
- if (enabled) {
- usage = UsageState::Running;
- } else {
- usage = UsageState::Stopped;
- }
- GetParams().status = ParameterStatus::Updated;
-}
-
-EffectAuxInfo::EffectAuxInfo() : EffectGeneric(EffectType::Aux) {}
-EffectAuxInfo::~EffectAuxInfo() = default;
-
-void EffectAuxInfo::Update(EffectInfo::InParams& in_params) {
- const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data());
- mix_id = in_params.mix_id;
- processing_order = in_params.processing_order;
- GetParams() = *aux_params;
- enabled = in_params.is_enabled;
-
- if (in_params.is_new || skipped) {
- skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0;
- if (skipped) {
- return;
- }
-
- // There's two AuxInfos which are an identical size, the first one is managed by the cpu,
- // the second is managed by the dsp. All we care about is managing the DSP one
- send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP);
- send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2);
-
- recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP);
- recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2);
- }
-}
-
-void EffectAuxInfo::UpdateForCommandGeneration() {
- if (enabled) {
- usage = UsageState::Running;
- } else {
- usage = UsageState::Stopped;
- }
-}
-
-VAddr EffectAuxInfo::GetSendInfo() const {
- return send_info;
-}
-
-VAddr EffectAuxInfo::GetSendBuffer() const {
- return send_buffer;
-}
-
-VAddr EffectAuxInfo::GetRecvInfo() const {
- return recv_info;
-}
-
-VAddr EffectAuxInfo::GetRecvBuffer() const {
- return recv_buffer;
-}
-
-EffectDelay::EffectDelay() : EffectGeneric(EffectType::Delay) {}
-EffectDelay::~EffectDelay() = default;
-
-void EffectDelay::Update(EffectInfo::InParams& in_params) {
- const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data());
- auto& params = GetParams();
- if (!ValidChannelCountForEffect(delay_params->max_channels)) {
- return;
- }
-
- const auto last_status = params.status;
- mix_id = in_params.mix_id;
- processing_order = in_params.processing_order;
- params = *delay_params;
- if (!ValidChannelCountForEffect(delay_params->channels)) {
- params.channels = params.max_channels;
- }
- enabled = in_params.is_enabled;
-
- if (last_status != ParameterStatus::Updated) {
- params.status = last_status;
- }
-
- if (in_params.is_new || skipped) {
- usage = UsageState::Initialized;
- params.status = ParameterStatus::Initialized;
- skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
- }
-}
-
-void EffectDelay::UpdateForCommandGeneration() {
- if (enabled) {
- usage = UsageState::Running;
- } else {
- usage = UsageState::Stopped;
- }
- GetParams().status = ParameterStatus::Updated;
-}
-
-EffectBufferMixer::EffectBufferMixer() : EffectGeneric(EffectType::BufferMixer) {}
-EffectBufferMixer::~EffectBufferMixer() = default;
-
-void EffectBufferMixer::Update(EffectInfo::InParams& in_params) {
- mix_id = in_params.mix_id;
- processing_order = in_params.processing_order;
- GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data());
- enabled = in_params.is_enabled;
-}
-
-void EffectBufferMixer::UpdateForCommandGeneration() {
- if (enabled) {
- usage = UsageState::Running;
- } else {
- usage = UsageState::Stopped;
- }
-}
-
-EffectReverb::EffectReverb() : EffectGeneric(EffectType::Reverb) {}
-EffectReverb::~EffectReverb() = default;
-
-void EffectReverb::Update(EffectInfo::InParams& in_params) {
- const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data());
- auto& params = GetParams();
- if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
- return;
- }
-
- const auto last_status = params.status;
- mix_id = in_params.mix_id;
- processing_order = in_params.processing_order;
- params = *reverb_params;
- if (!ValidChannelCountForEffect(reverb_params->channels)) {
- params.channels = params.max_channels;
- }
- enabled = in_params.is_enabled;
-
- if (last_status != ParameterStatus::Updated) {
- params.status = last_status;
- }
-
- if (in_params.is_new || skipped) {
- usage = UsageState::Initialized;
- params.status = ParameterStatus::Initialized;
- skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
- }
-}
-
-void EffectReverb::UpdateForCommandGeneration() {
- if (enabled) {
- usage = UsageState::Running;
- } else {
- usage = UsageState::Stopped;
- }
- GetParams().status = ParameterStatus::Updated;
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h
deleted file mode 100644
index 5e0655dd7..000000000
--- a/src/audio_core/effect_context.h
+++ /dev/null
@@ -1,350 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <memory>
-#include <vector>
-#include "audio_core/common.h"
-#include "audio_core/delay_line.h"
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/swap.h"
-
-namespace AudioCore {
-enum class EffectType : u8 {
- Invalid = 0,
- BufferMixer = 1,
- Aux = 2,
- Delay = 3,
- Reverb = 4,
- I3dl2Reverb = 5,
- BiquadFilter = 6,
-};
-
-enum class UsageStatus : u8 {
- Invalid = 0,
- New = 1,
- Initialized = 2,
- Used = 3,
- Removed = 4,
-};
-
-enum class UsageState {
- Invalid = 0,
- Initialized = 1,
- Running = 2,
- Stopped = 3,
-};
-
-enum class ParameterStatus : u8 {
- Initialized = 0,
- Updating = 1,
- Updated = 2,
-};
-
-struct BufferMixerParams {
- std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{};
- std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{};
- std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{};
- s32_le count{};
-};
-static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size");
-
-struct AuxInfoDSP {
- u32_le read_offset{};
- u32_le write_offset{};
- u32_le remaining{};
- INSERT_PADDING_WORDS(13);
-};
-static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size");
-
-struct AuxInfo {
- std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{};
- std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{};
- u32_le count{};
- s32_le sample_rate{};
- s32_le sample_count{};
- s32_le mix_buffer_count{};
- u64_le send_buffer_info{};
- u64_le send_buffer_base{};
-
- u64_le return_buffer_info{};
- u64_le return_buffer_base{};
-};
-static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
-
-struct I3dl2ReverbParams {
- std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
- std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
- u16_le max_channels{};
- u16_le channel_count{};
- INSERT_PADDING_BYTES(1);
- u32_le sample_rate{};
- f32 room_hf{};
- f32 hf_reference{};
- f32 decay_time{};
- f32 hf_decay_ratio{};
- f32 room{};
- f32 reflection{};
- f32 reverb{};
- f32 diffusion{};
- f32 reflection_delay{};
- f32 reverb_delay{};
- f32 density{};
- f32 dry_gain{};
- ParameterStatus status{};
- INSERT_PADDING_BYTES(3);
-};
-static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size");
-
-struct BiquadFilterParams {
- std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
- std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
- std::array<s16_le, 3> numerator;
- std::array<s16_le, 2> denominator;
- s8 channel_count{};
- ParameterStatus status{};
-};
-static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size");
-
-struct DelayParams {
- std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
- std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
- u16_le max_channels{};
- u16_le channels{};
- s32_le max_delay{};
- s32_le delay{};
- s32_le sample_rate{};
- s32_le gain{};
- s32_le feedback_gain{};
- s32_le out_gain{};
- s32_le dry_gain{};
- s32_le channel_spread{};
- s32_le low_pass{};
- ParameterStatus status{};
- INSERT_PADDING_BYTES(3);
-};
-static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size");
-
-struct ReverbParams {
- std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
- std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
- u16_le max_channels{};
- u16_le channels{};
- s32_le sample_rate{};
- s32_le mode0{};
- s32_le mode0_gain{};
- s32_le pre_delay{};
- s32_le mode1{};
- s32_le mode1_gain{};
- s32_le decay{};
- s32_le hf_decay_ratio{};
- s32_le coloration{};
- s32_le reverb_gain{};
- s32_le out_gain{};
- s32_le dry_gain{};
- ParameterStatus status{};
- INSERT_PADDING_BYTES(3);
-};
-static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size");
-
-class EffectInfo {
-public:
- struct InParams {
- EffectType type{};
- u8 is_new{};
- u8 is_enabled{};
- INSERT_PADDING_BYTES(1);
- s32_le mix_id{};
- u64_le buffer_address{};
- u64_le buffer_size{};
- s32_le processing_order{};
- INSERT_PADDING_BYTES(4);
- union {
- std::array<u8, 0xa0> raw;
- };
- };
- static_assert(sizeof(InParams) == 0xc0, "InParams is an invalid size");
-
- struct OutParams {
- UsageStatus status{};
- INSERT_PADDING_BYTES(15);
- };
- static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size");
-};
-
-struct AuxAddress {
- VAddr send_dsp_info{};
- VAddr send_buffer_base{};
- VAddr return_dsp_info{};
- VAddr return_buffer_base{};
-};
-
-class EffectBase {
-public:
- explicit EffectBase(EffectType effect_type_);
- virtual ~EffectBase();
-
- virtual void Update(EffectInfo::InParams& in_params) = 0;
- virtual void UpdateForCommandGeneration() = 0;
- [[nodiscard]] UsageState GetUsage() const;
- [[nodiscard]] EffectType GetType() const;
- [[nodiscard]] bool IsEnabled() const;
- [[nodiscard]] s32 GetMixID() const;
- [[nodiscard]] s32 GetProcessingOrder() const;
- [[nodiscard]] std::vector<u8>& GetWorkBuffer();
- [[nodiscard]] const std::vector<u8>& GetWorkBuffer() const;
-
-protected:
- UsageState usage{UsageState::Invalid};
- EffectType effect_type{};
- s32 mix_id{};
- s32 processing_order{};
- bool enabled = false;
- std::vector<u8> work_buffer{};
-};
-
-template <typename T>
-class EffectGeneric : public EffectBase {
-public:
- explicit EffectGeneric(EffectType effect_type_) : EffectBase(effect_type_) {}
-
- T& GetParams() {
- return internal_params;
- }
-
- const T& GetParams() const {
- return internal_params;
- }
-
-private:
- T internal_params{};
-};
-
-class EffectStubbed : public EffectBase {
-public:
- explicit EffectStubbed();
- ~EffectStubbed() override;
-
- void Update(EffectInfo::InParams& in_params) override;
- void UpdateForCommandGeneration() override;
-};
-
-struct I3dl2ReverbState {
- f32 lowpass_0{};
- f32 lowpass_1{};
- f32 lowpass_2{};
-
- DelayLineBase early_delay_line{};
- std::array<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{};
- f32 early_gain{};
- f32 late_gain{};
-
- u32 early_to_late_taps{};
- std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{};
- std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{};
- std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{};
- f32 last_reverb_echo{};
- DelayLineBase center_delay_line{};
- std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{};
- std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{};
- f32 dry_gain{};
-};
-
-class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
-public:
- explicit EffectI3dl2Reverb();
- ~EffectI3dl2Reverb() override;
-
- void Update(EffectInfo::InParams& in_params) override;
- void UpdateForCommandGeneration() override;
-
- I3dl2ReverbState& GetState();
- const I3dl2ReverbState& GetState() const;
-
-private:
- bool skipped = false;
- I3dl2ReverbState state{};
-};
-
-class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
-public:
- explicit EffectBiquadFilter();
- ~EffectBiquadFilter() override;
-
- void Update(EffectInfo::InParams& in_params) override;
- void UpdateForCommandGeneration() override;
-};
-
-class EffectAuxInfo : public EffectGeneric<AuxInfo> {
-public:
- explicit EffectAuxInfo();
- ~EffectAuxInfo() override;
-
- void Update(EffectInfo::InParams& in_params) override;
- void UpdateForCommandGeneration() override;
- [[nodiscard]] VAddr GetSendInfo() const;
- [[nodiscard]] VAddr GetSendBuffer() const;
- [[nodiscard]] VAddr GetRecvInfo() const;
- [[nodiscard]] VAddr GetRecvBuffer() const;
-
-private:
- VAddr send_info{};
- VAddr send_buffer{};
- VAddr recv_info{};
- VAddr recv_buffer{};
- bool skipped = false;
- AuxAddress addresses{};
-};
-
-class EffectDelay : public EffectGeneric<DelayParams> {
-public:
- explicit EffectDelay();
- ~EffectDelay() override;
-
- void Update(EffectInfo::InParams& in_params) override;
- void UpdateForCommandGeneration() override;
-
-private:
- bool skipped = false;
-};
-
-class EffectBufferMixer : public EffectGeneric<BufferMixerParams> {
-public:
- explicit EffectBufferMixer();
- ~EffectBufferMixer() override;
-
- void Update(EffectInfo::InParams& in_params) override;
- void UpdateForCommandGeneration() override;
-};
-
-class EffectReverb : public EffectGeneric<ReverbParams> {
-public:
- explicit EffectReverb();
- ~EffectReverb() override;
-
- void Update(EffectInfo::InParams& in_params) override;
- void UpdateForCommandGeneration() override;
-
-private:
- bool skipped = false;
-};
-
-class EffectContext {
-public:
- explicit EffectContext(std::size_t effect_count_);
- ~EffectContext();
-
- [[nodiscard]] std::size_t GetCount() const;
- [[nodiscard]] EffectBase* GetInfo(std::size_t i);
- [[nodiscard]] EffectBase* RetargetEffect(std::size_t i, EffectType effect);
- [[nodiscard]] const EffectBase* GetInfo(std::size_t i) const;
-
-private:
- std::size_t effect_count{};
- std::vector<std::unique_ptr<EffectBase>> effects;
-};
-} // namespace AudioCore
diff --git a/src/audio_core/in/audio_in.cpp b/src/audio_core/in/audio_in.cpp
new file mode 100644
index 000000000..91ccd5ad7
--- /dev/null
+++ b/src/audio_core/in/audio_in.cpp
@@ -0,0 +1,100 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/audio_in_manager.h"
+#include "audio_core/in/audio_in.h"
+#include "core/hle/kernel/k_event.h"
+
+namespace AudioCore::AudioIn {
+
+In::In(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_)
+ : manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event,
+ session_id_} {}
+
+void In::Free() {
+ std::scoped_lock l{parent_mutex};
+ manager.ReleaseSessionId(system.GetSessionId());
+}
+
+System& In::GetSystem() {
+ return system;
+}
+
+AudioIn::State In::GetState() {
+ std::scoped_lock l{parent_mutex};
+ return system.GetState();
+}
+
+Result In::StartSystem() {
+ std::scoped_lock l{parent_mutex};
+ return system.Start();
+}
+
+void In::StartSession() {
+ std::scoped_lock l{parent_mutex};
+ system.StartSession();
+}
+
+Result In::StopSystem() {
+ std::scoped_lock l{parent_mutex};
+ return system.Stop();
+}
+
+Result In::AppendBuffer(const AudioInBuffer& buffer, u64 tag) {
+ std::scoped_lock l{parent_mutex};
+
+ if (system.AppendBuffer(buffer, tag)) {
+ return ResultSuccess;
+ }
+ return Service::Audio::ERR_BUFFER_COUNT_EXCEEDED;
+}
+
+void In::ReleaseAndRegisterBuffers() {
+ std::scoped_lock l{parent_mutex};
+ if (system.GetState() == State::Started) {
+ system.ReleaseBuffers();
+ system.RegisterBuffers();
+ }
+}
+
+bool In::FlushAudioInBuffers() {
+ std::scoped_lock l{parent_mutex};
+ return system.FlushAudioInBuffers();
+}
+
+u32 In::GetReleasedBuffers(std::span<u64> tags) {
+ std::scoped_lock l{parent_mutex};
+ return system.GetReleasedBuffers(tags);
+}
+
+Kernel::KReadableEvent& In::GetBufferEvent() {
+ std::scoped_lock l{parent_mutex};
+ return event->GetReadableEvent();
+}
+
+f32 In::GetVolume() const {
+ std::scoped_lock l{parent_mutex};
+ return system.GetVolume();
+}
+
+void In::SetVolume(f32 volume) {
+ std::scoped_lock l{parent_mutex};
+ system.SetVolume(volume);
+}
+
+bool In::ContainsAudioBuffer(u64 tag) const {
+ std::scoped_lock l{parent_mutex};
+ return system.ContainsAudioBuffer(tag);
+}
+
+u32 In::GetBufferCount() const {
+ std::scoped_lock l{parent_mutex};
+ return system.GetBufferCount();
+}
+
+u64 In::GetPlayedSampleCount() const {
+ std::scoped_lock l{parent_mutex};
+ return system.GetPlayedSampleCount();
+}
+
+} // namespace AudioCore::AudioIn
diff --git a/src/audio_core/in/audio_in.h b/src/audio_core/in/audio_in.h
new file mode 100644
index 000000000..092ab7236
--- /dev/null
+++ b/src/audio_core/in/audio_in.h
@@ -0,0 +1,147 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+
+#include "audio_core/in/audio_in_system.h"
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace AudioCore::AudioIn {
+class Manager;
+
+/**
+ * Interface between the service and audio in system. Mainly responsible for forwarding service
+ * calls to the system.
+ */
+class In {
+public:
+ explicit In(Core::System& system, Manager& manager, Kernel::KEvent* event, size_t session_id);
+
+ /**
+ * Free this audio in from the audio in manager.
+ */
+ void Free();
+
+ /**
+ * Get this audio in's system.
+ */
+ System& GetSystem();
+
+ /**
+ * Get the current state.
+ *
+ * @return Started or Stopped.
+ */
+ AudioIn::State GetState();
+
+ /**
+ * Start the system
+ *
+ * @return Result code
+ */
+ Result StartSystem();
+
+ /**
+ * Start the system's device session.
+ */
+ void StartSession();
+
+ /**
+ * Stop the system.
+ *
+ * @return Result code
+ */
+ Result StopSystem();
+
+ /**
+ * Append a new buffer to the system, the buffer event will be signalled when it is filled.
+ *
+ * @param buffer - The new buffer to append.
+ * @param tag - Unique tag for this buffer.
+ * @return Result code.
+ */
+ Result AppendBuffer(const AudioInBuffer& buffer, u64 tag);
+
+ /**
+ * Release all completed buffers, and register any appended.
+ */
+ void ReleaseAndRegisterBuffers();
+
+ /**
+ * Flush all buffers.
+ */
+ bool FlushAudioInBuffers();
+
+ /**
+ * Get all of the currently released buffers.
+ *
+ * @param tags - Output container for the buffer tags which were released.
+ * @return The number of buffers released.
+ */
+ u32 GetReleasedBuffers(std::span<u64> tags);
+
+ /**
+ * Get the buffer event for this audio in, this event will be signalled when a buffer is filled.
+ *
+ * @return The buffer event.
+ */
+ Kernel::KReadableEvent& GetBufferEvent();
+
+ /**
+ * Get the current system volume.
+ *
+ * @return The current volume.
+ */
+ f32 GetVolume() const;
+
+ /**
+ * Set the system volume.
+ *
+ * @param volume - The volume to set.
+ */
+ void SetVolume(f32 volume);
+
+ /**
+ * Check if a buffer is in the system.
+ *
+ * @param tag - The tag to search for.
+ * @return True if the buffer is in the system, otherwise false.
+ */
+ bool ContainsAudioBuffer(u64 tag) const;
+
+ /**
+ * Get the maximum number of buffers.
+ *
+ * @return The maximum number of buffers.
+ */
+ u32 GetBufferCount() const;
+
+ /**
+ * Get the total played sample count for this audio in.
+ *
+ * @return The played sample count.
+ */
+ u64 GetPlayedSampleCount() const;
+
+private:
+ /// The AudioIn::Manager this audio in is registered with
+ Manager& manager;
+ /// Manager's mutex
+ std::recursive_mutex& parent_mutex;
+ /// Buffer event, signalled when buffers are ready to be released
+ Kernel::KEvent* event;
+ /// Main audio in system
+ System system;
+};
+
+} // namespace AudioCore::AudioIn
diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp
new file mode 100644
index 000000000..e7f918a47
--- /dev/null
+++ b/src/audio_core/in/audio_in_system.cpp
@@ -0,0 +1,221 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <mutex>
+#include "audio_core/audio_event.h"
+#include "audio_core/audio_manager.h"
+#include "audio_core/in/audio_in_system.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hle/kernel/k_event.h"
+
+namespace AudioCore::AudioIn {
+
+System::System(Core::System& system_, Kernel::KEvent* event_, const size_t session_id_)
+ : system{system_}, buffer_event{event_},
+ session_id{session_id_}, session{std::make_unique<DeviceSession>(system_)} {}
+
+System::~System() {
+ Finalize();
+}
+
+void System::Finalize() {
+ Stop();
+ session->Finalize();
+ buffer_event->GetWritableEvent().Signal();
+}
+
+void System::StartSession() {
+ session->Start();
+}
+
+size_t System::GetSessionId() const {
+ return session_id;
+}
+
+std::string_view System::GetDefaultDeviceName() const {
+ return "BuiltInHeadset";
+}
+
+std::string_view System::GetDefaultUacDeviceName() const {
+ return "Uac";
+}
+
+Result System::IsConfigValid(const std::string_view device_name,
+ const AudioInParameter& in_params) const {
+ if ((device_name.size() > 0) &&
+ (device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) {
+ return Service::Audio::ERR_INVALID_DEVICE_NAME;
+ }
+
+ if (in_params.sample_rate != TargetSampleRate && in_params.sample_rate > 0) {
+ return Service::Audio::ERR_INVALID_SAMPLE_RATE;
+ }
+
+ return ResultSuccess;
+}
+
+Result System::Initialize(std::string& device_name, const AudioInParameter& in_params,
+ const u32 handle_, const u64 applet_resource_user_id_) {
+ auto result{IsConfigValid(device_name, in_params)};
+ if (result.IsError()) {
+ return result;
+ }
+
+ handle = handle_;
+ applet_resource_user_id = applet_resource_user_id_;
+ if (device_name.empty() || device_name[0] == '\0') {
+ name = std::string(GetDefaultDeviceName());
+ } else {
+ name = std::move(device_name);
+ }
+
+ sample_rate = TargetSampleRate;
+ sample_format = SampleFormat::PcmInt16;
+ channel_count = in_params.channel_count <= 2 ? 2 : 6;
+ volume = 1.0f;
+ is_uac = name == "Uac";
+ return ResultSuccess;
+}
+
+Result System::Start() {
+ if (state != State::Stopped) {
+ return Service::Audio::ERR_OPERATION_FAILED;
+ }
+
+ session->Initialize(name, sample_format, channel_count, session_id, handle,
+ applet_resource_user_id, Sink::StreamType::In);
+ session->SetVolume(volume);
+ session->Start();
+ state = State::Started;
+
+ std::vector<AudioBuffer> buffers_to_flush{};
+ buffers.RegisterBuffers(buffers_to_flush);
+ session->AppendBuffers(buffers_to_flush);
+ session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
+
+ return ResultSuccess;
+}
+
+Result System::Stop() {
+ if (state == State::Started) {
+ session->Stop();
+ session->SetVolume(0.0f);
+ state = State::Stopped;
+ }
+
+ return ResultSuccess;
+}
+
+bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
+ if (buffers.GetTotalBufferCount() == BufferCount) {
+ return false;
+ }
+
+ const auto timestamp{buffers.GetNextTimestamp()};
+ const AudioBuffer new_buffer{
+ .start_timestamp = timestamp,
+ .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
+ .played_timestamp = 0,
+ .samples = buffer.samples,
+ .tag = tag,
+ .size = buffer.size,
+ };
+
+ buffers.AppendBuffer(new_buffer);
+ RegisterBuffers();
+
+ return true;
+}
+
+void System::RegisterBuffers() {
+ if (state == State::Started) {
+ std::vector<AudioBuffer> registered_buffers{};
+ buffers.RegisterBuffers(registered_buffers);
+ session->AppendBuffers(registered_buffers);
+ }
+}
+
+void System::ReleaseBuffers() {
+ bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)};
+
+ if (signal) {
+ // Signal if any buffer was released, or if none are registered, we need more.
+ buffer_event->GetWritableEvent().Signal();
+ }
+}
+
+u32 System::GetReleasedBuffers(std::span<u64> tags) {
+ return buffers.GetReleasedBuffers(tags);
+}
+
+bool System::FlushAudioInBuffers() {
+ if (state != State::Started) {
+ return false;
+ }
+
+ u32 buffers_released{};
+ buffers.FlushBuffers(buffers_released);
+
+ if (buffers_released > 0) {
+ buffer_event->GetWritableEvent().Signal();
+ }
+ return true;
+}
+
+u16 System::GetChannelCount() const {
+ return channel_count;
+}
+
+u32 System::GetSampleRate() const {
+ return sample_rate;
+}
+
+SampleFormat System::GetSampleFormat() const {
+ return sample_format;
+}
+
+State System::GetState() {
+ switch (state) {
+ case State::Started:
+ case State::Stopped:
+ return state;
+ default:
+ LOG_ERROR(Service_Audio, "AudioIn invalid state!");
+ state = State::Stopped;
+ break;
+ }
+ return state;
+}
+
+std::string System::GetName() const {
+ return name;
+}
+
+f32 System::GetVolume() const {
+ return volume;
+}
+
+void System::SetVolume(const f32 volume_) {
+ volume = volume_;
+ session->SetVolume(volume_);
+}
+
+bool System::ContainsAudioBuffer(const u64 tag) const {
+ return buffers.ContainsBuffer(tag);
+}
+
+u32 System::GetBufferCount() const {
+ return buffers.GetAppendedRegisteredCount();
+}
+
+u64 System::GetPlayedSampleCount() const {
+ return session->GetPlayedSampleCount();
+}
+
+bool System::IsUac() const {
+ return is_uac;
+}
+
+} // namespace AudioCore::AudioIn
diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h
new file mode 100644
index 000000000..b9dc0e60f
--- /dev/null
+++ b/src/audio_core/in/audio_in_system.h
@@ -0,0 +1,275 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <span>
+#include <string>
+
+#include "audio_core/common/common.h"
+#include "audio_core/device/audio_buffers.h"
+#include "audio_core/device/device_session.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+class KEvent;
+}
+
+namespace AudioCore::AudioIn {
+
+constexpr SessionTypes SessionType = SessionTypes::AudioIn;
+
+struct AudioInParameter {
+ /* 0x0 */ s32_le sample_rate;
+ /* 0x4 */ u16_le channel_count;
+ /* 0x6 */ u16_le reserved;
+};
+static_assert(sizeof(AudioInParameter) == 0x8, "AudioInParameter is an invalid size");
+
+struct AudioInParameterInternal {
+ /* 0x0 */ u32_le sample_rate;
+ /* 0x4 */ u32_le channel_count;
+ /* 0x8 */ u32_le sample_format;
+ /* 0xC */ u32_le state;
+};
+static_assert(sizeof(AudioInParameterInternal) == 0x10,
+ "AudioInParameterInternal is an invalid size");
+
+struct AudioInBuffer {
+ /* 0x00 */ AudioInBuffer* next;
+ /* 0x08 */ VAddr samples;
+ /* 0x10 */ u64 capacity;
+ /* 0x18 */ u64 size;
+ /* 0x20 */ u64 offset;
+};
+static_assert(sizeof(AudioInBuffer) == 0x28, "AudioInBuffer is an invalid size");
+
+enum class State {
+ Started,
+ Stopped,
+};
+
+/**
+ * Controls and drives audio input.
+ */
+class System {
+public:
+ explicit System(Core::System& system, Kernel::KEvent* event, size_t session_id);
+ ~System();
+
+ /**
+ * Get the default audio input device name.
+ *
+ * @return The default audio input device name.
+ */
+ std::string_view GetDefaultDeviceName() const;
+
+ /**
+ * Get the default USB audio input device name.
+ * This is preferred over non-USB as some games refuse to work with the BuiltInHeadset
+ * (e.g Let's Sing).
+ *
+ * @return The default USB audio input device name.
+ */
+ std::string_view GetDefaultUacDeviceName() const;
+
+ /**
+ * Is the given initialize config valid?
+ *
+ * @param device_name - The name of the requested input device.
+ * @param in_params - Input parameters, see AudioInParameter.
+ * @return Result code.
+ */
+ Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params) const;
+
+ /**
+ * Initialize this system.
+ *
+ * @param device_name - The name of the requested input device.
+ * @param in_params - Input parameters, see AudioInParameter.
+ * @param handle - Unused.
+ * @param applet_resource_user_id - Unused.
+ * @return Result code.
+ */
+ Result Initialize(std::string& device_name, const AudioInParameter& in_params, u32 handle,
+ u64 applet_resource_user_id);
+
+ /**
+ * Start this system.
+ *
+ * @return Result code.
+ */
+ Result Start();
+
+ /**
+ * Stop this system.
+ *
+ * @return Result code.
+ */
+ Result Stop();
+
+ /**
+ * Finalize this system.
+ */
+ void Finalize();
+
+ /**
+ * Start this system's device session.
+ */
+ void StartSession();
+
+ /**
+ * Get this system's id.
+ */
+ size_t GetSessionId() const;
+
+ /**
+ * Append a new buffer to the device.
+ *
+ * @param buffer - New buffer to append.
+ * @param tag - Unique tag of the buffer.
+ * @return True if the buffer was appended, otherwise false.
+ */
+ bool AppendBuffer(const AudioInBuffer& buffer, u64 tag);
+
+ /**
+ * Register all appended buffers.
+ */
+ void RegisterBuffers();
+
+ /**
+ * Release all registered buffers.
+ */
+ void ReleaseBuffers();
+
+ /**
+ * Get all released buffers.
+ *
+ * @param tags - Container to be filled with the released buffers' tags.
+ * @return The number of buffers released.
+ */
+ u32 GetReleasedBuffers(std::span<u64> tags);
+
+ /**
+ * Flush all appended and registered buffers.
+ *
+ * @return True if buffers were successfully flushed, otherwise false.
+ */
+ bool FlushAudioInBuffers();
+
+ /**
+ * Get this system's current channel count.
+ *
+ * @return The channel count.
+ */
+ u16 GetChannelCount() const;
+
+ /**
+ * Get this system's current sample rate.
+ *
+ * @return The sample rate.
+ */
+ u32 GetSampleRate() const;
+
+ /**
+ * Get this system's current sample format.
+ *
+ * @return The sample format.
+ */
+ SampleFormat GetSampleFormat() const;
+
+ /**
+ * Get this system's current state.
+ *
+ * @return The current state.
+ */
+ State GetState();
+
+ /**
+ * Get this system's name.
+ *
+ * @return The system's name.
+ */
+ std::string GetName() const;
+
+ /**
+ * Get this system's current volume.
+ *
+ * @return The system's current volume.
+ */
+ f32 GetVolume() const;
+
+ /**
+ * Set this system's current volume.
+ *
+ * @param volume The new volume.
+ */
+ void SetVolume(f32 volume);
+
+ /**
+ * Does the system contain this buffer?
+ *
+ * @param tag - Unique tag to search for.
+ * @return True if the buffer is in the system, otherwise false.
+ */
+ bool ContainsAudioBuffer(u64 tag) const;
+
+ /**
+ * Get the maximum number of usable buffers (default 32).
+ *
+ * @return The number of buffers.
+ */
+ u32 GetBufferCount() const;
+
+ /**
+ * Get the total number of samples played by this system.
+ *
+ * @return The number of samples.
+ */
+ u64 GetPlayedSampleCount() const;
+
+ /**
+ * Is this system using a USB device?
+ *
+ * @return True if using a USB device, otherwise false.
+ */
+ bool IsUac() const;
+
+private:
+ /// Core system
+ Core::System& system;
+ /// (Unused)
+ u32 handle{};
+ /// (Unused)
+ u64 applet_resource_user_id{};
+ /// Buffer event, signalled when a buffer is ready
+ Kernel::KEvent* buffer_event;
+ /// Session id of this system
+ size_t session_id{};
+ /// Device session for this system
+ std::unique_ptr<DeviceSession> session;
+ /// Audio buffers in use by this system
+ AudioBuffers<BufferCount> buffers{BufferCount};
+ /// Sample rate of this system
+ u32 sample_rate{};
+ /// Sample format of this system
+ SampleFormat sample_format{SampleFormat::PcmInt16};
+ /// Channel count of this system
+ u16 channel_count{};
+ /// State of this system
+ std::atomic<State> state{State::Stopped};
+ /// Name of this system
+ std::string name{};
+ /// Volume of this system
+ f32 volume{1.0f};
+ /// Is this system's device USB?
+ bool is_uac{false};
+};
+
+} // namespace AudioCore::AudioIn
diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp
deleted file mode 100644
index 9b4ca1851..000000000
--- a/src/audio_core/info_updater.cpp
+++ /dev/null
@@ -1,513 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "audio_core/behavior_info.h"
-#include "audio_core/effect_context.h"
-#include "audio_core/info_updater.h"
-#include "audio_core/memory_pool.h"
-#include "audio_core/mix_context.h"
-#include "audio_core/sink_context.h"
-#include "audio_core/splitter_context.h"
-#include "audio_core/voice_context.h"
-#include "common/logging/log.h"
-
-namespace AudioCore {
-
-InfoUpdater::InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_,
- BehaviorInfo& behavior_info_)
- : in_params(in_params_), out_params(out_params_), behavior_info(behavior_info_) {
- ASSERT(
- AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader)));
- std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader));
- output_header.total_size = sizeof(AudioCommon::UpdateDataHeader);
-}
-
-InfoUpdater::~InfoUpdater() = default;
-
-bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) {
- if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) {
- LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}",
- sizeof(BehaviorInfo::InParams), input_header.size.behavior);
- return false;
- }
-
- if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
- sizeof(BehaviorInfo::InParams))) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
-
- BehaviorInfo::InParams behavior_in{};
- std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams));
- input_offset += sizeof(BehaviorInfo::InParams);
-
- // Make sure it's an audio revision we can actually support
- if (!AudioCommon::IsValidRevision(behavior_in.revision)) {
- LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision);
- return false;
- }
-
- // Make sure that our behavior info revision matches the input
- if (in_behavior_info.GetUserRevision() != behavior_in.revision) {
- LOG_ERROR(Audio,
- "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
- in_behavior_info.GetUserRevision(), behavior_in.revision);
- return false;
- }
-
- // Update behavior info flags
- in_behavior_info.ClearError();
- in_behavior_info.UpdateFlags(behavior_in.flags);
-
- return true;
-}
-
-bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) {
- const auto memory_pool_count = memory_pool_info.size();
- const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count;
- const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count;
-
- if (input_header.size.memory_pool != total_memory_pool_in) {
- LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}",
- total_memory_pool_in, input_header.size.memory_pool);
- return false;
- }
-
- if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
-
- std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count);
- std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count);
-
- std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in);
- input_offset += total_memory_pool_in;
-
- // Update our memory pools
- for (std::size_t i = 0; i < memory_pool_count; i++) {
- if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) {
- LOG_ERROR(Audio, "Failed to update memory pool {}!", i);
- return false;
- }
- }
-
- if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset,
- sizeof(BehaviorInfo::InParams))) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
-
- std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out);
- output_offset += total_memory_pool_out;
- output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out);
- return true;
-}
-
-bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
- const auto voice_count = voice_context.GetVoiceCount();
- const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams);
- std::vector<VoiceChannelResource::InParams> resources_in(voice_count);
-
- if (input_header.size.voice_channel_resource != voice_size) {
- LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}",
- voice_size, input_header.size.voice_channel_resource);
- return false;
- }
-
- if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
-
- std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size);
- input_offset += voice_size;
-
- // Update our channel resources
- for (std::size_t i = 0; i < voice_count; i++) {
- // Grab our channel resource
- auto& resource = voice_context.GetChannelResource(i);
- resource.Update(resources_in[i]);
- }
-
- return true;
-}
-
-bool InfoUpdater::UpdateVoices(VoiceContext& voice_context,
- [[maybe_unused]] std::vector<ServerMemoryPoolInfo>& memory_pool_info,
- [[maybe_unused]] VAddr audio_codec_dsp_addr) {
- const auto voice_count = voice_context.GetVoiceCount();
- std::vector<VoiceInfo::InParams> voice_in(voice_count);
- std::vector<VoiceInfo::OutParams> voice_out(voice_count);
-
- const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams);
- const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams);
-
- if (input_header.size.voice != voice_in_size) {
- LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}",
- voice_in_size, input_header.size.voice);
- return false;
- }
-
- if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
-
- std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size);
- input_offset += voice_in_size;
-
- // Set all voices to not be in use
- for (std::size_t i = 0; i < voice_count; i++) {
- voice_context.GetInfo(i).GetInParams().in_use = false;
- }
-
- // Update our voices
- for (std::size_t i = 0; i < voice_count; i++) {
- auto& voice_in_params = voice_in[i];
- const auto channel_count = static_cast<std::size_t>(voice_in_params.channel_count);
- // Skip if it's not currently in use
- if (!voice_in_params.is_in_use) {
- continue;
- }
- // Voice states for each channel
- std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{};
- ASSERT(static_cast<std::size_t>(voice_in_params.id) < voice_count);
-
- // Grab our current voice info
- auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(voice_in_params.id));
-
- ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT);
-
- // Get all our channel voice states
- for (std::size_t channel = 0; channel < channel_count; channel++) {
- voice_states[channel] =
- &voice_context.GetState(voice_in_params.voice_channel_resource_ids[channel]);
- }
-
- if (voice_in_params.is_new) {
- // Default our values for our voice
- voice_info.Initialize();
-
- // Zero out our voice states
- for (std::size_t channel = 0; channel < channel_count; channel++) {
- std::memset(voice_states[channel], 0, sizeof(VoiceState));
- }
- }
-
- // Update our voice
- voice_info.UpdateParameters(voice_in_params, behavior_info);
- // TODO(ogniK): Handle mapping errors with behavior info based on in params response
-
- // Update our wave buffers
- voice_info.UpdateWaveBuffers(voice_in_params, voice_states, behavior_info);
- voice_info.WriteOutStatus(voice_out[i], voice_in_params, voice_states);
- }
-
- if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
- std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size);
- output_offset += voice_out_size;
- output_header.size.voice = static_cast<u32>(voice_out_size);
- return true;
-}
-
-bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) {
- const auto effect_count = effect_context.GetCount();
- std::vector<EffectInfo::InParams> effect_in(effect_count);
- std::vector<EffectInfo::OutParams> effect_out(effect_count);
-
- const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams);
- const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams);
-
- if (input_header.size.effect != total_effect_in) {
- LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}",
- total_effect_in, input_header.size.effect);
- return false;
- }
-
- if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
-
- std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in);
- input_offset += total_effect_in;
-
- // Update effects
- for (std::size_t i = 0; i < effect_count; i++) {
- auto* info = effect_context.GetInfo(i);
- if (effect_in[i].type != info->GetType()) {
- info = effect_context.RetargetEffect(i, effect_in[i].type);
- }
-
- info->Update(effect_in[i]);
-
- if ((!is_active && info->GetUsage() != UsageState::Initialized) ||
- info->GetUsage() == UsageState::Stopped) {
- effect_out[i].status = UsageStatus::Removed;
- } else {
- effect_out[i].status = UsageStatus::Used;
- }
- }
-
- if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
-
- std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out);
- output_offset += total_effect_out;
- output_header.size.effect = static_cast<u32>(total_effect_out);
-
- return true;
-}
-
-bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
- std::size_t start_offset = input_offset;
- std::size_t bytes_read{};
- // Update splitter context
- if (!splitter_context.Update(in_params, input_offset, bytes_read)) {
- LOG_ERROR(Audio, "Failed to update splitter context!");
- return false;
- }
-
- const auto consumed = input_offset - start_offset;
-
- if (input_header.size.splitter != consumed) {
- LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}",
- bytes_read, input_header.size.splitter);
- return false;
- }
-
- return true;
-}
-
-ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
- SplitterContext& splitter_context,
- EffectContext& effect_context) {
- std::vector<MixInfo::InParams> mix_in_params;
-
- if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
- // If we're not dirty, get ALL mix in parameters
- const auto context_mix_count = mix_context.GetCount();
- const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams);
- if (input_header.size.mixer != total_mix_in) {
- LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
- total_mix_in, input_header.size.mixer);
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- mix_in_params.resize(context_mix_count);
- std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in);
-
- input_offset += total_mix_in;
- } else {
- // Only update the "dirty" mixes
- MixInfo::DirtyHeader dirty_header{};
- if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
- sizeof(MixInfo::DirtyHeader))) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader));
- input_offset += sizeof(MixInfo::DirtyHeader);
-
- const auto total_mix_in =
- dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader);
-
- if (input_header.size.mixer != total_mix_in) {
- LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
- total_mix_in, input_header.size.mixer);
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- if (dirty_header.mixer_count != 0) {
- mix_in_params.resize(dirty_header.mixer_count);
- std::memcpy(mix_in_params.data(), in_params.data() + input_offset,
- mix_in_params.size() * sizeof(MixInfo::InParams));
- input_offset += mix_in_params.size() * sizeof(MixInfo::InParams);
- }
- }
-
- // Get our total input count
- const auto mix_count = mix_in_params.size();
-
- if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
- // Only verify our buffer count if we're not dirty
- std::size_t total_buffer_count{};
- for (std::size_t i = 0; i < mix_count; i++) {
- const auto& in = mix_in_params[i];
- total_buffer_count += in.buffer_count;
- if (static_cast<std::size_t>(in.dest_mix_id) > mix_count &&
- in.dest_mix_id != AudioCommon::NO_MIX && in.mix_id != AudioCommon::FINAL_MIX) {
- LOG_ERROR(
- Audio,
- "Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}",
- in.mix_id, in.dest_mix_id, mix_buffer_count);
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
- }
-
- if (total_buffer_count > mix_buffer_count) {
- LOG_ERROR(Audio,
- "Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}",
- mix_buffer_count, total_buffer_count);
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
- }
-
- if (mix_buffer_count == 0) {
- LOG_ERROR(Audio, "No mix buffers!");
- return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
- }
-
- bool should_sort = false;
- for (std::size_t i = 0; i < mix_count; i++) {
- const auto& mix_in = mix_in_params[i];
- std::size_t target_mix{};
- if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
- target_mix = mix_in.mix_id;
- } else {
- // Non dirty supported games just use i instead of the actual mix_id
- target_mix = i;
- }
- auto& mix_info = mix_context.GetInfo(target_mix);
- auto& mix_info_params = mix_info.GetInParams();
- if (mix_info_params.in_use != mix_in.in_use) {
- mix_info_params.in_use = mix_in.in_use;
- mix_info.ResetEffectProcessingOrder();
- should_sort = true;
- }
-
- if (mix_in.in_use) {
- should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info,
- splitter_context, effect_context);
- }
- }
-
- if (should_sort && behavior_info.IsSplitterSupported()) {
- // Sort our splitter data
- if (!mix_context.TsortInfo(splitter_context)) {
- return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED;
- }
- }
-
- // TODO(ogniK): Sort when splitter is suppoorted
-
- return ResultSuccess;
-}
-
-bool InfoUpdater::UpdateSinks(SinkContext& sink_context) {
- const auto sink_count = sink_context.GetCount();
- std::vector<SinkInfo::InParams> sink_in_params(sink_count);
- const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams);
-
- if (input_header.size.sink != total_sink_in) {
- LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}",
- total_sink_in, input_header.size.effect);
- return false;
- }
-
- if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
-
- std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in);
- input_offset += total_sink_in;
-
- // TODO(ogniK): Properly update sinks
- if (!sink_in_params.empty()) {
- sink_context.UpdateMainSink(sink_in_params[0]);
- }
-
- output_header.size.sink = static_cast<u32>(0x20 * sink_count);
- output_offset += 0x20 * sink_count;
- return true;
-}
-
-bool InfoUpdater::UpdatePerformanceBuffer() {
- output_header.size.performance = 0x10;
- output_offset += 0x10;
- return true;
-}
-
-bool InfoUpdater::UpdateErrorInfo([[maybe_unused]] BehaviorInfo& in_behavior_info) {
- const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams);
-
- if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
-
- BehaviorInfo::OutParams behavior_info_out{};
- behavior_info.CopyErrorInfo(behavior_info_out);
-
- std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out);
- output_offset += total_beahvior_info_out;
- output_header.size.behavior = total_beahvior_info_out;
-
- return true;
-}
-
-struct RendererInfo {
- u64_le elasped_frame_count{};
- INSERT_PADDING_WORDS(2);
-};
-static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
-
-bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) {
- const auto total_renderer_info_out = sizeof(RendererInfo);
- if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
- RendererInfo out{};
- out.elasped_frame_count = elapsed_frame_count;
- std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out);
- output_offset += total_renderer_info_out;
- output_header.size.render_info = total_renderer_info_out;
-
- return true;
-}
-
-bool InfoUpdater::CheckConsumedSize() const {
- if (output_offset != out_params.size()) {
- LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining",
- output_offset, out_params.size(), out_params.size() - output_offset);
- return false;
- }
- /*if (input_offset != in_params.size()) {
- LOG_ERROR(Audio, "Input is not consumed!");
- return false;
- }*/
- return true;
-}
-
-bool InfoUpdater::WriteOutputHeader() {
- if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0,
- sizeof(AudioCommon::UpdateDataHeader))) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
- output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION;
- const auto& sz = output_header.size;
- output_header.total_size += sz.behavior + sz.memory_pool + sz.voice +
- sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink +
- sz.performance + sz.splitter + sz.render_info;
-
- std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader));
- return true;
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h
deleted file mode 100644
index d315c91ed..000000000
--- a/src/audio_core/info_updater.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <vector>
-#include "audio_core/common.h"
-#include "common/common_types.h"
-
-namespace AudioCore {
-
-class BehaviorInfo;
-class ServerMemoryPoolInfo;
-class VoiceContext;
-class EffectContext;
-class MixContext;
-class SinkContext;
-class SplitterContext;
-
-class InfoUpdater {
-public:
- // TODO(ogniK): Pass process handle when we support it
- InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_,
- BehaviorInfo& behavior_info_);
- ~InfoUpdater();
-
- bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info);
- bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info);
- bool UpdateVoiceChannelResources(VoiceContext& voice_context);
- bool UpdateVoices(VoiceContext& voice_context,
- std::vector<ServerMemoryPoolInfo>& memory_pool_info,
- VAddr audio_codec_dsp_addr);
- bool UpdateEffects(EffectContext& effect_context, bool is_active);
- bool UpdateSplitterInfo(SplitterContext& splitter_context);
- ResultCode UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
- SplitterContext& splitter_context, EffectContext& effect_context);
- bool UpdateSinks(SinkContext& sink_context);
- bool UpdatePerformanceBuffer();
- bool UpdateErrorInfo(BehaviorInfo& in_behavior_info);
- bool UpdateRendererInfo(std::size_t elapsed_frame_count);
- bool CheckConsumedSize() const;
-
- bool WriteOutputHeader();
-
-private:
- const std::vector<u8>& in_params;
- std::vector<u8>& out_params;
- BehaviorInfo& behavior_info;
-
- AudioCommon::UpdateDataHeader input_header{};
- AudioCommon::UpdateDataHeader output_header{};
-
- std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)};
- std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)};
-};
-
-} // namespace AudioCore
diff --git a/src/audio_core/memory_pool.cpp b/src/audio_core/memory_pool.cpp
deleted file mode 100644
index 6b6908d26..000000000
--- a/src/audio_core/memory_pool.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "audio_core/memory_pool.h"
-#include "common/logging/log.h"
-
-namespace AudioCore {
-
-ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default;
-ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default;
-
-bool ServerMemoryPoolInfo::Update(const InParams& in_params, OutParams& out_params) {
- // Our state does not need to be changed
- if (in_params.state != State::RequestAttach && in_params.state != State::RequestDetach) {
- return true;
- }
-
- // Address or size is null
- if (in_params.address == 0 || in_params.size == 0) {
- LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}",
- in_params.address, in_params.size);
- return false;
- }
-
- // Address or size is not aligned
- if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) {
- LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}",
- in_params.address, in_params.size);
- return false;
- }
-
- if (in_params.state == State::RequestAttach) {
- cpu_address = in_params.address;
- size = in_params.size;
- used = true;
- out_params.state = State::Attached;
- } else {
- // Unexpected address
- if (cpu_address != in_params.address) {
- LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}",
- cpu_address, in_params.address);
- return false;
- }
-
- if (size != in_params.size) {
- LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size,
- in_params.size);
- return false;
- }
-
- cpu_address = 0;
- size = 0;
- used = false;
- out_params.state = State::Detached;
- }
- return true;
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/memory_pool.h b/src/audio_core/memory_pool.h
deleted file mode 100644
index 3e9e777ae..000000000
--- a/src/audio_core/memory_pool.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/swap.h"
-
-namespace AudioCore {
-
-class ServerMemoryPoolInfo {
-public:
- ServerMemoryPoolInfo();
- ~ServerMemoryPoolInfo();
-
- enum class State : u32_le {
- Invalid = 0x0,
- Aquired = 0x1,
- RequestDetach = 0x2,
- Detached = 0x3,
- RequestAttach = 0x4,
- Attached = 0x5,
- Released = 0x6,
- };
-
- struct InParams {
- u64_le address{};
- u64_le size{};
- State state{};
- INSERT_PADDING_WORDS(3);
- };
- static_assert(sizeof(InParams) == 0x20, "InParams are an invalid size");
-
- struct OutParams {
- State state{};
- INSERT_PADDING_WORDS(3);
- };
- static_assert(sizeof(OutParams) == 0x10, "OutParams are an invalid size");
-
- bool Update(const InParams& in_params, OutParams& out_params);
-
-private:
- // There's another entry here which is the DSP address, however since we're not talking to the
- // DSP we can just use the same address provided by the guest without needing to remap
- u64_le cpu_address{};
- u64_le size{};
- bool used{};
-};
-
-} // namespace AudioCore
diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp
deleted file mode 100644
index 057aab5ad..000000000
--- a/src/audio_core/mix_context.cpp
+++ /dev/null
@@ -1,298 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-
-#include "audio_core/behavior_info.h"
-#include "audio_core/common.h"
-#include "audio_core/effect_context.h"
-#include "audio_core/mix_context.h"
-#include "audio_core/splitter_context.h"
-
-namespace AudioCore {
-MixContext::MixContext() = default;
-MixContext::~MixContext() = default;
-
-void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
- std::size_t effect_count) {
- info_count = mix_count;
- infos.resize(info_count);
- auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
- final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX;
- sorted_info.reserve(infos.size());
- for (auto& info : infos) {
- sorted_info.push_back(&info);
- }
-
- for (auto& info : infos) {
- info.SetEffectCount(effect_count);
- }
-
- // Only initialize our edge matrix and node states if splitters are supported
- if (behavior_info.IsSplitterSupported()) {
- node_states.Initialize(mix_count);
- edge_matrix.Initialize(mix_count);
- }
-}
-
-void MixContext::UpdateDistancesFromFinalMix() {
- // Set all distances to be invalid
- for (std::size_t i = 0; i < info_count; i++) {
- GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX;
- }
-
- for (std::size_t i = 0; i < info_count; i++) {
- auto& info = GetInfo(i);
- auto& in_params = info.GetInParams();
- // Populate our sorted info
- sorted_info[i] = &info;
-
- if (!in_params.in_use) {
- continue;
- }
-
- auto mix_id = in_params.mix_id;
- // Needs to be referenced out of scope
- s32 distance_to_final_mix{AudioCommon::FINAL_MIX};
- for (; distance_to_final_mix < static_cast<s32>(info_count); distance_to_final_mix++) {
- if (mix_id == AudioCommon::FINAL_MIX) {
- // If we're at the final mix, we're done
- break;
- } else if (mix_id == AudioCommon::NO_MIX) {
- // If we have no more mix ids, we're done
- distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
- break;
- } else {
- const auto& dest_mix = GetInfo(mix_id);
- const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance;
-
- if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) {
- // If our current mix isn't pointing to a final mix, follow through
- mix_id = dest_mix.GetInParams().dest_mix_id;
- } else {
- // Our current mix + 1 = final distance
- distance_to_final_mix = dest_mix_distance + 1;
- break;
- }
- }
- }
-
- // If we're out of range for our distance, mark it as no final mix
- if (distance_to_final_mix >= static_cast<s32>(info_count)) {
- distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
- }
-
- in_params.final_mix_distance = distance_to_final_mix;
- }
-}
-
-void MixContext::CalcMixBufferOffset() {
- s32 offset{};
- for (std::size_t i = 0; i < info_count; i++) {
- auto& info = GetSortedInfo(i);
- auto& in_params = info.GetInParams();
- if (in_params.in_use) {
- // Only update if in use
- in_params.buffer_offset = offset;
- offset += in_params.buffer_count;
- }
- }
-}
-
-void MixContext::SortInfo() {
- // Get the distance to the final mix
- UpdateDistancesFromFinalMix();
-
- // Sort based on the distance to the final mix
- std::sort(sorted_info.begin(), sorted_info.end(),
- [](const ServerMixInfo* lhs, const ServerMixInfo* rhs) {
- return lhs->GetInParams().final_mix_distance >
- rhs->GetInParams().final_mix_distance;
- });
-
- // Calculate the mix buffer offset
- CalcMixBufferOffset();
-}
-
-bool MixContext::TsortInfo(SplitterContext& splitter_context) {
- // If we're not using mixes, just calculate the mix buffer offset
- if (!splitter_context.UsingSplitter()) {
- CalcMixBufferOffset();
- return true;
- }
- // Sort our node states
- if (!node_states.Tsort(edge_matrix)) {
- return false;
- }
-
- // Get our sorted list
- const auto sorted_list = node_states.GetIndexList();
- std::size_t info_id{};
- for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) {
- // Set our sorted info
- sorted_info[info_id++] = &GetInfo(*itr);
- }
-
- // Calculate the mix buffer offset
- CalcMixBufferOffset();
- return true;
-}
-
-std::size_t MixContext::GetCount() const {
- return info_count;
-}
-
-ServerMixInfo& MixContext::GetInfo(std::size_t i) {
- ASSERT(i < info_count);
- return infos.at(i);
-}
-
-const ServerMixInfo& MixContext::GetInfo(std::size_t i) const {
- ASSERT(i < info_count);
- return infos.at(i);
-}
-
-ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) {
- ASSERT(i < info_count);
- return *sorted_info.at(i);
-}
-
-const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const {
- ASSERT(i < info_count);
- return *sorted_info.at(i);
-}
-
-ServerMixInfo& MixContext::GetFinalMixInfo() {
- return infos.at(AudioCommon::FINAL_MIX);
-}
-
-const ServerMixInfo& MixContext::GetFinalMixInfo() const {
- return infos.at(AudioCommon::FINAL_MIX);
-}
-
-EdgeMatrix& MixContext::GetEdgeMatrix() {
- return edge_matrix;
-}
-
-const EdgeMatrix& MixContext::GetEdgeMatrix() const {
- return edge_matrix;
-}
-
-ServerMixInfo::ServerMixInfo() {
- Cleanup();
-}
-ServerMixInfo::~ServerMixInfo() = default;
-
-const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const {
- return in_params;
-}
-
-ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
- return in_params;
-}
-
-bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
- BehaviorInfo& behavior_info, SplitterContext& splitter_context,
- EffectContext& effect_context) {
- in_params.volume = mix_in.volume;
- in_params.sample_rate = mix_in.sample_rate;
- in_params.buffer_count = mix_in.buffer_count;
- in_params.in_use = mix_in.in_use;
- in_params.mix_id = mix_in.mix_id;
- in_params.node_id = mix_in.node_id;
- for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) {
- std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(),
- in_params.mix_volume[i].begin());
- }
-
- bool require_sort = false;
-
- if (behavior_info.IsSplitterSupported()) {
- require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context);
- } else {
- in_params.dest_mix_id = mix_in.dest_mix_id;
- in_params.splitter_id = AudioCommon::NO_SPLITTER;
- }
-
- ResetEffectProcessingOrder();
- const auto effect_count = effect_context.GetCount();
- for (std::size_t i = 0; i < effect_count; i++) {
- auto* effect_info = effect_context.GetInfo(i);
- if (effect_info->GetMixID() == in_params.mix_id) {
- effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i);
- }
- }
-
- // TODO(ogniK): Update effect processing order
- return require_sort;
-}
-
-bool ServerMixInfo::HasAnyConnection() const {
- return in_params.splitter_id != AudioCommon::NO_SPLITTER ||
- in_params.mix_id != AudioCommon::NO_MIX;
-}
-
-void ServerMixInfo::Cleanup() {
- in_params.volume = 0.0f;
- in_params.sample_rate = 0;
- in_params.buffer_count = 0;
- in_params.in_use = false;
- in_params.mix_id = AudioCommon::NO_MIX;
- in_params.node_id = 0;
- in_params.buffer_offset = 0;
- in_params.dest_mix_id = AudioCommon::NO_MIX;
- in_params.splitter_id = AudioCommon::NO_SPLITTER;
- std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
-}
-
-void ServerMixInfo::SetEffectCount(std::size_t count) {
- effect_processing_order.resize(count);
- ResetEffectProcessingOrder();
-}
-
-void ServerMixInfo::ResetEffectProcessingOrder() {
- for (auto& order : effect_processing_order) {
- order = AudioCommon::NO_EFFECT_ORDER;
- }
-}
-
-s32 ServerMixInfo::GetEffectOrder(std::size_t i) const {
- return effect_processing_order.at(i);
-}
-
-bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
- SplitterContext& splitter_context) {
- // Mixes are identical
- if (in_params.dest_mix_id == mix_in.dest_mix_id &&
- in_params.splitter_id == mix_in.splitter_id &&
- ((in_params.splitter_id == AudioCommon::NO_SPLITTER) ||
- !splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) {
- return false;
- }
- // Remove current edges for mix id
- edge_matrix.RemoveEdges(in_params.mix_id);
- if (mix_in.dest_mix_id != AudioCommon::NO_MIX) {
- // If we have a valid destination mix id, set our edge matrix
- edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id);
- } else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) {
- // Recurse our splitter linked and set our edges
- auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id);
- const auto length = splitter_info.GetLength();
- for (s32 i = 0; i < length; i++) {
- const auto* splitter_destination =
- splitter_context.GetDestinationData(mix_in.splitter_id, i);
- if (splitter_destination == nullptr) {
- continue;
- }
- if (splitter_destination->ValidMixId()) {
- edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId());
- }
- }
- }
- in_params.dest_mix_id = mix_in.dest_mix_id;
- in_params.splitter_id = mix_in.splitter_id;
- return true;
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h
deleted file mode 100644
index 68bc673c6..000000000
--- a/src/audio_core/mix_context.h
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <vector>
-#include "audio_core/common.h"
-#include "audio_core/splitter_context.h"
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-
-namespace AudioCore {
-class BehaviorInfo;
-class EffectContext;
-
-class MixInfo {
-public:
- struct DirtyHeader {
- u32_le magic{};
- u32_le mixer_count{};
- INSERT_PADDING_BYTES(0x18);
- };
- static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size");
-
- struct InParams {
- float_le volume{};
- s32_le sample_rate{};
- s32_le buffer_count{};
- bool in_use{};
- INSERT_PADDING_BYTES(3);
- s32_le mix_id{};
- s32_le effect_count{};
- u32_le node_id{};
- INSERT_PADDING_WORDS(2);
- std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
- mix_volume{};
- s32_le dest_mix_id{};
- s32_le splitter_id{};
- INSERT_PADDING_WORDS(1);
- };
- static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size");
-};
-
-class ServerMixInfo {
-public:
- struct InParams {
- float volume{};
- s32 sample_rate{};
- s32 buffer_count{};
- bool in_use{};
- s32 mix_id{};
- u32 node_id{};
- std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
- mix_volume{};
- s32 dest_mix_id{};
- s32 splitter_id{};
- s32 buffer_offset{};
- s32 final_mix_distance{};
- };
- ServerMixInfo();
- ~ServerMixInfo();
-
- [[nodiscard]] const ServerMixInfo::InParams& GetInParams() const;
- [[nodiscard]] ServerMixInfo::InParams& GetInParams();
-
- bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
- BehaviorInfo& behavior_info, SplitterContext& splitter_context,
- EffectContext& effect_context);
- [[nodiscard]] bool HasAnyConnection() const;
- void Cleanup();
- void SetEffectCount(std::size_t count);
- void ResetEffectProcessingOrder();
- [[nodiscard]] s32 GetEffectOrder(std::size_t i) const;
-
-private:
- std::vector<s32> effect_processing_order;
- InParams in_params{};
- bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
- SplitterContext& splitter_context);
-};
-
-class MixContext {
-public:
- MixContext();
- ~MixContext();
-
- void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
- std::size_t effect_count);
- void SortInfo();
- bool TsortInfo(SplitterContext& splitter_context);
-
- [[nodiscard]] std::size_t GetCount() const;
- [[nodiscard]] ServerMixInfo& GetInfo(std::size_t i);
- [[nodiscard]] const ServerMixInfo& GetInfo(std::size_t i) const;
- [[nodiscard]] ServerMixInfo& GetSortedInfo(std::size_t i);
- [[nodiscard]] const ServerMixInfo& GetSortedInfo(std::size_t i) const;
- [[nodiscard]] ServerMixInfo& GetFinalMixInfo();
- [[nodiscard]] const ServerMixInfo& GetFinalMixInfo() const;
- [[nodiscard]] EdgeMatrix& GetEdgeMatrix();
- [[nodiscard]] const EdgeMatrix& GetEdgeMatrix() const;
-
-private:
- void CalcMixBufferOffset();
- void UpdateDistancesFromFinalMix();
-
- NodeStates node_states{};
- EdgeMatrix edge_matrix{};
- std::size_t info_count{};
- std::vector<ServerMixInfo> infos{};
- std::vector<ServerMixInfo*> sorted_info{};
-};
-} // namespace AudioCore
diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h
deleted file mode 100644
index 61a28d542..000000000
--- a/src/audio_core/null_sink.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "audio_core/sink.h"
-
-namespace AudioCore {
-
-class NullSink final : public Sink {
-public:
- explicit NullSink(std::string_view) {}
- ~NullSink() override = default;
-
- SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/,
- const std::string& /*name*/) override {
- return null_sink_stream;
- }
-
-private:
- struct NullSinkStreamImpl final : SinkStream {
- void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {}
-
- std::size_t SamplesInQueue(u32 /*num_channels*/) const override {
- return 0;
- }
-
- void Flush() override {}
- } null_sink_stream;
-};
-
-} // namespace AudioCore
diff --git a/src/audio_core/out/audio_out.cpp b/src/audio_core/out/audio_out.cpp
new file mode 100644
index 000000000..d3ee4f0eb
--- /dev/null
+++ b/src/audio_core/out/audio_out.cpp
@@ -0,0 +1,100 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/audio_out_manager.h"
+#include "audio_core/out/audio_out.h"
+#include "core/hle/kernel/k_event.h"
+
+namespace AudioCore::AudioOut {
+
+Out::Out(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_)
+ : manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event,
+ session_id_} {}
+
+void Out::Free() {
+ std::scoped_lock l{parent_mutex};
+ manager.ReleaseSessionId(system.GetSessionId());
+}
+
+System& Out::GetSystem() {
+ return system;
+}
+
+AudioOut::State Out::GetState() {
+ std::scoped_lock l{parent_mutex};
+ return system.GetState();
+}
+
+Result Out::StartSystem() {
+ std::scoped_lock l{parent_mutex};
+ return system.Start();
+}
+
+void Out::StartSession() {
+ std::scoped_lock l{parent_mutex};
+ system.StartSession();
+}
+
+Result Out::StopSystem() {
+ std::scoped_lock l{parent_mutex};
+ return system.Stop();
+}
+
+Result Out::AppendBuffer(const AudioOutBuffer& buffer, const u64 tag) {
+ std::scoped_lock l{parent_mutex};
+
+ if (system.AppendBuffer(buffer, tag)) {
+ return ResultSuccess;
+ }
+ return Service::Audio::ERR_BUFFER_COUNT_EXCEEDED;
+}
+
+void Out::ReleaseAndRegisterBuffers() {
+ std::scoped_lock l{parent_mutex};
+ if (system.GetState() == State::Started) {
+ system.ReleaseBuffers();
+ system.RegisterBuffers();
+ }
+}
+
+bool Out::FlushAudioOutBuffers() {
+ std::scoped_lock l{parent_mutex};
+ return system.FlushAudioOutBuffers();
+}
+
+u32 Out::GetReleasedBuffers(std::span<u64> tags) {
+ std::scoped_lock l{parent_mutex};
+ return system.GetReleasedBuffers(tags);
+}
+
+Kernel::KReadableEvent& Out::GetBufferEvent() {
+ std::scoped_lock l{parent_mutex};
+ return event->GetReadableEvent();
+}
+
+f32 Out::GetVolume() const {
+ std::scoped_lock l{parent_mutex};
+ return system.GetVolume();
+}
+
+void Out::SetVolume(const f32 volume) {
+ std::scoped_lock l{parent_mutex};
+ system.SetVolume(volume);
+}
+
+bool Out::ContainsAudioBuffer(const u64 tag) const {
+ std::scoped_lock l{parent_mutex};
+ return system.ContainsAudioBuffer(tag);
+}
+
+u32 Out::GetBufferCount() const {
+ std::scoped_lock l{parent_mutex};
+ return system.GetBufferCount();
+}
+
+u64 Out::GetPlayedSampleCount() const {
+ std::scoped_lock l{parent_mutex};
+ return system.GetPlayedSampleCount();
+}
+
+} // namespace AudioCore::AudioOut
diff --git a/src/audio_core/out/audio_out.h b/src/audio_core/out/audio_out.h
new file mode 100644
index 000000000..946f345c6
--- /dev/null
+++ b/src/audio_core/out/audio_out.h
@@ -0,0 +1,147 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+
+#include "audio_core/out/audio_out_system.h"
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace AudioCore::AudioOut {
+class Manager;
+
+/**
+ * Interface between the service and audio out system. Mainly responsible for forwarding service
+ * calls to the system.
+ */
+class Out {
+public:
+ explicit Out(Core::System& system, Manager& manager, Kernel::KEvent* event, size_t session_id);
+
+ /**
+ * Free this audio out from the audio out manager.
+ */
+ void Free();
+
+ /**
+ * Get this audio out's system.
+ */
+ System& GetSystem();
+
+ /**
+ * Get the current state.
+ *
+ * @return Started or Stopped.
+ */
+ AudioOut::State GetState();
+
+ /**
+ * Start the system
+ *
+ * @return Result code
+ */
+ Result StartSystem();
+
+ /**
+ * Start the system's device session.
+ */
+ void StartSession();
+
+ /**
+ * Stop the system.
+ *
+ * @return Result code
+ */
+ Result StopSystem();
+
+ /**
+ * Append a new buffer to the system, the buffer event will be signalled when it is filled.
+ *
+ * @param buffer - The new buffer to append.
+ * @param tag - Unique tag for this buffer.
+ * @return Result code.
+ */
+ Result AppendBuffer(const AudioOutBuffer& buffer, u64 tag);
+
+ /**
+ * Release all completed buffers, and register any appended.
+ */
+ void ReleaseAndRegisterBuffers();
+
+ /**
+ * Flush all buffers.
+ */
+ bool FlushAudioOutBuffers();
+
+ /**
+ * Get all of the currently released buffers.
+ *
+ * @param tags - Output container for the buffer tags which were released.
+ * @return The number of buffers released.
+ */
+ u32 GetReleasedBuffers(std::span<u64> tags);
+
+ /**
+ * Get the buffer event for this audio out, this event will be signalled when a buffer is
+ * filled.
+ * @return The buffer event.
+ */
+ Kernel::KReadableEvent& GetBufferEvent();
+
+ /**
+ * Get the current system volume.
+ *
+ * @return The current volume.
+ */
+ f32 GetVolume() const;
+
+ /**
+ * Set the system volume.
+ *
+ * @param volume - The volume to set.
+ */
+ void SetVolume(f32 volume);
+
+ /**
+ * Check if a buffer is in the system.
+ *
+ * @param tag - The tag to search for.
+ * @return True if the buffer is in the system, otherwise false.
+ */
+ bool ContainsAudioBuffer(u64 tag) const;
+
+ /**
+ * Get the maximum number of buffers.
+ *
+ * @return The maximum number of buffers.
+ */
+ u32 GetBufferCount() const;
+
+ /**
+ * Get the total played sample count for this audio out.
+ *
+ * @return The played sample count.
+ */
+ u64 GetPlayedSampleCount() const;
+
+private:
+ /// The AudioOut::Manager this audio out is registered with
+ Manager& manager;
+ /// Manager's mutex
+ std::recursive_mutex& parent_mutex;
+ /// Buffer event, signalled when buffers are ready to be released
+ Kernel::KEvent* event;
+ /// Main audio out system
+ System system;
+};
+
+} // namespace AudioCore::AudioOut
diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp
new file mode 100644
index 000000000..8b907590a
--- /dev/null
+++ b/src/audio_core/out/audio_out_system.cpp
@@ -0,0 +1,216 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <mutex>
+
+#include "audio_core/audio_event.h"
+#include "audio_core/audio_manager.h"
+#include "audio_core/out/audio_out_system.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hle/kernel/k_event.h"
+
+namespace AudioCore::AudioOut {
+
+System::System(Core::System& system_, Kernel::KEvent* event_, size_t session_id_)
+ : system{system_}, buffer_event{event_},
+ session_id{session_id_}, session{std::make_unique<DeviceSession>(system_)} {}
+
+System::~System() {
+ Finalize();
+}
+
+void System::Finalize() {
+ Stop();
+ session->Finalize();
+ buffer_event->GetWritableEvent().Signal();
+}
+
+std::string_view System::GetDefaultOutputDeviceName() const {
+ return "DeviceOut";
+}
+
+Result System::IsConfigValid(std::string_view device_name,
+ const AudioOutParameter& in_params) const {
+ if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) {
+ return Service::Audio::ERR_INVALID_DEVICE_NAME;
+ }
+
+ if (in_params.sample_rate != TargetSampleRate && in_params.sample_rate > 0) {
+ return Service::Audio::ERR_INVALID_SAMPLE_RATE;
+ }
+
+ if (in_params.channel_count == 0 || in_params.channel_count == 2 ||
+ in_params.channel_count == 6) {
+ return ResultSuccess;
+ }
+
+ return Service::Audio::ERR_INVALID_CHANNEL_COUNT;
+}
+
+Result System::Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle_,
+ u64& applet_resource_user_id_) {
+ auto result = IsConfigValid(device_name, in_params);
+ if (result.IsError()) {
+ return result;
+ }
+
+ handle = handle_;
+ applet_resource_user_id = applet_resource_user_id_;
+ if (device_name.empty() || device_name[0] == '\0') {
+ name = std::string(GetDefaultOutputDeviceName());
+ } else {
+ name = std::move(device_name);
+ }
+
+ sample_rate = TargetSampleRate;
+ sample_format = SampleFormat::PcmInt16;
+ channel_count = in_params.channel_count <= 2 ? 2 : 6;
+ volume = 1.0f;
+ return ResultSuccess;
+}
+
+void System::StartSession() {
+ session->Start();
+}
+
+size_t System::GetSessionId() const {
+ return session_id;
+}
+
+Result System::Start() {
+ if (state != State::Stopped) {
+ return Service::Audio::ERR_OPERATION_FAILED;
+ }
+
+ session->Initialize(name, sample_format, channel_count, session_id, handle,
+ applet_resource_user_id, Sink::StreamType::Out);
+ session->SetVolume(volume);
+ session->Start();
+ state = State::Started;
+
+ std::vector<AudioBuffer> buffers_to_flush{};
+ buffers.RegisterBuffers(buffers_to_flush);
+ session->AppendBuffers(buffers_to_flush);
+ session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
+
+ return ResultSuccess;
+}
+
+Result System::Stop() {
+ if (state == State::Started) {
+ session->Stop();
+ session->SetVolume(0.0f);
+ state = State::Stopped;
+ }
+
+ return ResultSuccess;
+}
+
+bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
+ if (buffers.GetTotalBufferCount() == BufferCount) {
+ return false;
+ }
+
+ const auto timestamp{buffers.GetNextTimestamp()};
+ const AudioBuffer new_buffer{
+ .start_timestamp = timestamp,
+ .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
+ .played_timestamp = 0,
+ .samples = buffer.samples,
+ .tag = tag,
+ .size = buffer.size,
+ };
+
+ buffers.AppendBuffer(new_buffer);
+ RegisterBuffers();
+
+ return true;
+}
+
+void System::RegisterBuffers() {
+ if (state == State::Started) {
+ std::vector<AudioBuffer> registered_buffers{};
+ buffers.RegisterBuffers(registered_buffers);
+ session->AppendBuffers(registered_buffers);
+ }
+}
+
+void System::ReleaseBuffers() {
+ bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)};
+ if (signal) {
+ // Signal if any buffer was released, or if none are registered, we need more.
+ buffer_event->GetWritableEvent().Signal();
+ }
+}
+
+u32 System::GetReleasedBuffers(std::span<u64> tags) {
+ return buffers.GetReleasedBuffers(tags);
+}
+
+bool System::FlushAudioOutBuffers() {
+ if (state != State::Started) {
+ return false;
+ }
+
+ u32 buffers_released{};
+ buffers.FlushBuffers(buffers_released);
+
+ if (buffers_released > 0) {
+ buffer_event->GetWritableEvent().Signal();
+ }
+ return true;
+}
+
+u16 System::GetChannelCount() const {
+ return channel_count;
+}
+
+u32 System::GetSampleRate() const {
+ return sample_rate;
+}
+
+SampleFormat System::GetSampleFormat() const {
+ return sample_format;
+}
+
+State System::GetState() {
+ switch (state) {
+ case State::Started:
+ case State::Stopped:
+ return state;
+ default:
+ LOG_ERROR(Service_Audio, "AudioOut invalid state!");
+ state = State::Stopped;
+ break;
+ }
+ return state;
+}
+
+std::string System::GetName() const {
+ return name;
+}
+
+f32 System::GetVolume() const {
+ return volume;
+}
+
+void System::SetVolume(const f32 volume_) {
+ volume = volume_;
+ session->SetVolume(volume_);
+}
+
+bool System::ContainsAudioBuffer(const u64 tag) const {
+ return buffers.ContainsBuffer(tag);
+}
+
+u32 System::GetBufferCount() const {
+ return buffers.GetAppendedRegisteredCount();
+}
+
+u64 System::GetPlayedSampleCount() const {
+ return session->GetPlayedSampleCount();
+}
+
+} // namespace AudioCore::AudioOut
diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h
new file mode 100644
index 000000000..0817b2f37
--- /dev/null
+++ b/src/audio_core/out/audio_out_system.h
@@ -0,0 +1,257 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <span>
+#include <string>
+
+#include "audio_core/common/common.h"
+#include "audio_core/device/audio_buffers.h"
+#include "audio_core/device/device_session.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+class KEvent;
+}
+
+namespace AudioCore::AudioOut {
+
+constexpr SessionTypes SessionType = SessionTypes::AudioOut;
+
+struct AudioOutParameter {
+ /* 0x0 */ s32_le sample_rate;
+ /* 0x4 */ u16_le channel_count;
+ /* 0x6 */ u16_le reserved;
+};
+static_assert(sizeof(AudioOutParameter) == 0x8, "AudioOutParameter is an invalid size");
+
+struct AudioOutParameterInternal {
+ /* 0x0 */ u32_le sample_rate;
+ /* 0x4 */ u32_le channel_count;
+ /* 0x8 */ u32_le sample_format;
+ /* 0xC */ u32_le state;
+};
+static_assert(sizeof(AudioOutParameterInternal) == 0x10,
+ "AudioOutParameterInternal is an invalid size");
+
+struct AudioOutBuffer {
+ /* 0x00 */ AudioOutBuffer* next;
+ /* 0x08 */ VAddr samples;
+ /* 0x10 */ u64 capacity;
+ /* 0x18 */ u64 size;
+ /* 0x20 */ u64 offset;
+};
+static_assert(sizeof(AudioOutBuffer) == 0x28, "AudioOutBuffer is an invalid size");
+
+enum class State {
+ Started,
+ Stopped,
+};
+
+/**
+ * Controls and drives audio output.
+ */
+class System {
+public:
+ explicit System(Core::System& system, Kernel::KEvent* event, size_t session_id);
+ ~System();
+
+ /**
+ * Get the default audio output device name.
+ *
+ * @return The default audio output device name.
+ */
+ std::string_view GetDefaultOutputDeviceName() const;
+
+ /**
+ * Is the given initialize config valid?
+ *
+ * @param device_name - The name of the requested output device.
+ * @param in_params - Input parameters, see AudioOutParameter.
+ * @return Result code.
+ */
+ Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) const;
+
+ /**
+ * Initialize this system.
+ *
+ * @param device_name - The name of the requested output device.
+ * @param in_params - Input parameters, see AudioOutParameter.
+ * @param handle - Unused.
+ * @param applet_resource_user_id - Unused.
+ * @return Result code.
+ */
+ Result Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle,
+ u64& applet_resource_user_id);
+
+ /**
+ * Start this system.
+ *
+ * @return Result code.
+ */
+ Result Start();
+
+ /**
+ * Stop this system.
+ *
+ * @return Result code.
+ */
+ Result Stop();
+
+ /**
+ * Finalize this system.
+ */
+ void Finalize();
+
+ /**
+ * Start this system's device session.
+ */
+ void StartSession();
+
+ /**
+ * Get this system's id.
+ */
+ size_t GetSessionId() const;
+
+ /**
+ * Append a new buffer to the device.
+ *
+ * @param buffer - New buffer to append.
+ * @param tag - Unique tag of the buffer.
+ * @return True if the buffer was appended, otherwise false.
+ */
+ bool AppendBuffer(const AudioOutBuffer& buffer, u64 tag);
+
+ /**
+ * Register all appended buffers.
+ */
+ void RegisterBuffers();
+
+ /**
+ * Release all registered buffers.
+ */
+ void ReleaseBuffers();
+
+ /**
+ * Get all released buffers.
+ *
+ * @param tags - Container to be filled with the released buffers' tags.
+ * @return The number of buffers released.
+ */
+ u32 GetReleasedBuffers(std::span<u64> tags);
+
+ /**
+ * Flush all appended and registered buffers.
+ *
+ * @return True if buffers were successfully flushed, otherwise false.
+ */
+ bool FlushAudioOutBuffers();
+
+ /**
+ * Get this system's current channel count.
+ *
+ * @return The channel count.
+ */
+ u16 GetChannelCount() const;
+
+ /**
+ * Get this system's current sample rate.
+ *
+ * @return The sample rate.
+ */
+ u32 GetSampleRate() const;
+
+ /**
+ * Get this system's current sample format.
+ *
+ * @return The sample format.
+ */
+ SampleFormat GetSampleFormat() const;
+
+ /**
+ * Get this system's current state.
+ *
+ * @return The current state.
+ */
+ State GetState();
+
+ /**
+ * Get this system's name.
+ *
+ * @return The system's name.
+ */
+ std::string GetName() const;
+
+ /**
+ * Get this system's current volume.
+ *
+ * @return The system's current volume.
+ */
+ f32 GetVolume() const;
+
+ /**
+ * Set this system's current volume.
+ *
+ * @param volume The new volume.
+ */
+ void SetVolume(f32 volume);
+
+ /**
+ * Does the system contain this buffer?
+ *
+ * @param tag - Unique tag to search for.
+ * @return True if the buffer is in the system, otherwise false.
+ */
+ bool ContainsAudioBuffer(u64 tag) const;
+
+ /**
+ * Get the maximum number of usable buffers (default 32).
+ *
+ * @return The number of buffers.
+ */
+ u32 GetBufferCount() const;
+
+ /**
+ * Get the total number of samples played by this system.
+ *
+ * @return The number of samples.
+ */
+ u64 GetPlayedSampleCount() const;
+
+private:
+ /// Core system
+ Core::System& system;
+ /// (Unused)
+ u32 handle{};
+ /// (Unused)
+ u64 applet_resource_user_id{};
+ /// Buffer event, signalled when a buffer is ready
+ Kernel::KEvent* buffer_event;
+ /// Session id of this system
+ size_t session_id{};
+ /// Device session for this system
+ std::unique_ptr<DeviceSession> session;
+ /// Audio buffers in use by this system
+ AudioBuffers<BufferCount> buffers{BufferCount};
+ /// Sample rate of this system
+ u32 sample_rate{};
+ /// Sample format of this system
+ SampleFormat sample_format{SampleFormat::PcmInt16};
+ /// Channel count of this system
+ u16 channel_count{};
+ /// State of this system
+ std::atomic<State> state{State::Stopped};
+ /// Name of this system
+ std::string name{};
+ /// Volume of this system
+ f32 volume{1.0f};
+};
+
+} // namespace AudioCore::AudioOut
diff --git a/src/audio_core/renderer/adsp/adsp.cpp b/src/audio_core/renderer/adsp/adsp.cpp
new file mode 100644
index 000000000..a28395663
--- /dev/null
+++ b/src/audio_core/renderer/adsp/adsp.cpp
@@ -0,0 +1,118 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/adsp.h"
+#include "audio_core/renderer/adsp/command_buffer.h"
+#include "audio_core/sink/sink.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer::ADSP {
+
+ADSP::ADSP(Core::System& system_, Sink::Sink& sink_)
+ : system{system_}, memory{system.Memory()}, sink{sink_} {}
+
+ADSP::~ADSP() {
+ ClearCommandBuffers();
+}
+
+State ADSP::GetState() const {
+ if (running) {
+ return State::Started;
+ }
+ return State::Stopped;
+}
+
+AudioRenderer_Mailbox* ADSP::GetRenderMailbox() {
+ return &render_mailbox;
+}
+
+void ADSP::ClearRemainCount(const u32 session_id) {
+ render_mailbox.ClearRemainCount(session_id);
+}
+
+u64 ADSP::GetSignalledTick() const {
+ return render_mailbox.GetSignalledTick();
+}
+
+u64 ADSP::GetTimeTaken() const {
+ return render_mailbox.GetRenderTimeTaken();
+}
+
+u64 ADSP::GetRenderTimeTaken(const u32 session_id) {
+ return render_mailbox.GetCommandBuffer(session_id).render_time_taken;
+}
+
+u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
+ return render_mailbox.GetRemainCommandCount(session_id);
+}
+
+void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) {
+ render_mailbox.SetCommandBuffer(session_id, command_buffer);
+}
+
+u64 ADSP::GetRenderingStartTick(const u32 session_id) {
+ return render_mailbox.GetSignalledTick() +
+ render_mailbox.GetCommandBuffer(session_id).render_time_taken;
+}
+
+bool ADSP::Start() {
+ if (running) {
+ return running;
+ }
+
+ running = true;
+ systems_active++;
+ audio_renderer = std::make_unique<AudioRenderer>(system);
+ audio_renderer->Start(&render_mailbox);
+ render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_InitializeOK);
+ if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
+ LOG_ERROR(
+ Service_Audio,
+ "Host Audio Renderer -- Failed to receive initialize message response from ADSP!");
+ }
+ return running;
+}
+
+void ADSP::Stop() {
+ systems_active--;
+ if (running && systems_active == 0) {
+ {
+ std::scoped_lock l{mailbox_lock};
+ render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Shutdown);
+ if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_Shutdown) {
+ LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
+ "message response from ADSP!");
+ }
+ }
+ audio_renderer->Stop();
+ running = false;
+ }
+}
+
+void ADSP::Signal() {
+ const auto signalled_tick{system.CoreTiming().GetClockTicks()};
+ render_mailbox.SetSignalledTick(signalled_tick);
+ render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Render);
+}
+
+void ADSP::Wait() {
+ std::scoped_lock l{mailbox_lock};
+ auto response{render_mailbox.HostWaitMessage()};
+ if (response != RenderMessage::AudioRenderer_RenderResponse) {
+ LOG_ERROR(Service_Audio, "Invalid ADSP response message, expected 0x{:02X}, got 0x{:02X}",
+ static_cast<u32>(RenderMessage::AudioRenderer_RenderResponse),
+ static_cast<u32>(response));
+ }
+
+ ClearCommandBuffers();
+}
+
+void ADSP::ClearCommandBuffers() {
+ render_mailbox.ClearCommandBuffers();
+}
+
+} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h
new file mode 100644
index 000000000..f7a2f25e4
--- /dev/null
+++ b/src/audio_core/renderer/adsp/adsp.h
@@ -0,0 +1,171 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+
+#include "audio_core/renderer/adsp/audio_renderer.h"
+#include "common/common_types.h"
+
+namespace Core {
+namespace Memory {
+class Memory;
+}
+class System;
+} // namespace Core
+
+namespace AudioCore {
+namespace Sink {
+class Sink;
+}
+
+namespace AudioRenderer::ADSP {
+struct CommandBuffer;
+
+enum class State {
+ Started,
+ Stopped,
+};
+
+/**
+ * Represents the ADSP embedded within the audio sysmodule.
+ * This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot.
+ *
+ * The kernel will run apps you program for it, Nintendo have the following:
+ *
+ * Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all
+ * audio samples end up, and we skip it entirely, since we have very different backends and
+ * mixing is implicitly handled by the OS (but also due to lack of research/simplicity).
+ *
+ * AudioRenderer - Receives command lists generated by the audio render
+ * system, processes them, and sends the samples to Gmix.
+ *
+ * OpusDecoder - Contains libopus, and controls processing Opus audio and sends it to Gmix.
+ * Not much research done here, TODO if needed.
+ *
+ * We only implement the AudioRenderer for now.
+ *
+ * Communication for the apps is done through mailboxes, and some shared memory.
+ */
+class ADSP {
+public:
+ explicit ADSP(Core::System& system, Sink::Sink& sink);
+ ~ADSP();
+
+ /**
+ * Start the ADSP.
+ *
+ * @return True if started or already running, otherwise false.
+ */
+ bool Start();
+
+ /**
+ * Stop the ADSP.
+ */
+ void Stop();
+
+ /**
+ * Get the ADSP's state.
+ *
+ * @return Started or Stopped.
+ */
+ State GetState() const;
+
+ /**
+ * Get the AudioRenderer mailbox to communicate with it.
+ *
+ * @return The AudioRenderer mailbox.
+ */
+ AudioRenderer_Mailbox* GetRenderMailbox();
+
+ /**
+ * Get the tick the ADSP was signalled.
+ *
+ * @return The tick the ADSP was signalled.
+ */
+ u64 GetSignalledTick() const;
+
+ /**
+ * Get the total time it took for the ADSP to run the last command lists (both command lists).
+ *
+ * @return The tick the ADSP was signalled.
+ */
+ u64 GetTimeTaken() const;
+
+ /**
+ * Get the last time a given command list took to run.
+ *
+ * @param session_id - The session id to check (0 or 1).
+ * @return The time it took.
+ */
+ u64 GetRenderTimeTaken(u32 session_id);
+
+ /**
+ * Clear the remaining command count for a given session.
+ *
+ * @param session_id - The session id to check (0 or 1).
+ */
+ void ClearRemainCount(u32 session_id);
+
+ /**
+ * Get the remaining number of commands left to process for a command list.
+ *
+ * @param session_id - The session id to check (0 or 1).
+ * @return The number of commands remaining.
+ */
+ u32 GetRemainCommandCount(u32 session_id) const;
+
+ /**
+ * Get the last tick a command list started processing.
+ *
+ * @param session_id - The session id to check (0 or 1).
+ * @return The last tick the given command list started.
+ */
+ u64 GetRenderingStartTick(u32 session_id);
+
+ /**
+ * Set a command buffer to be processed.
+ *
+ * @param session_id - The session id to check (0 or 1).
+ * @param command_buffer - The command buffer to process.
+ */
+ void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer);
+
+ /**
+ * Clear the command buffers (does not clear the time taken or the remaining command count)
+ */
+ void ClearCommandBuffers();
+
+ /**
+ * Signal the AudioRenderer to begin processing.
+ */
+ void Signal();
+
+ /**
+ * Wait for the AudioRenderer to finish processing.
+ */
+ void Wait();
+
+private:
+ /// Core system
+ Core::System& system;
+ /// Core memory
+ Core::Memory::Memory& memory;
+ /// Number of systems active, used to prevent accidental shutdowns
+ u8 systems_active{0};
+ /// ADSP running state
+ std::atomic<bool> running{false};
+ /// Output sink used by the ADSP
+ Sink::Sink& sink;
+ /// AudioRenderer app
+ std::unique_ptr<AudioRenderer> audio_renderer{};
+ /// Communication for the AudioRenderer
+ AudioRenderer_Mailbox render_mailbox{};
+ /// Mailbox lock ffor the render mailbox
+ std::mutex mailbox_lock;
+};
+
+} // namespace AudioRenderer::ADSP
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp
new file mode 100644
index 000000000..d982ef630
--- /dev/null
+++ b/src/audio_core/renderer/adsp/audio_renderer.cpp
@@ -0,0 +1,219 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <chrono>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/adsp/audio_renderer.h"
+#include "audio_core/sink/sink.h"
+#include "common/logging/log.h"
+#include "common/microprofile.h"
+#include "common/thread.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+
+MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
+
+namespace AudioCore::AudioRenderer::ADSP {
+
+void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) {
+ adsp_messages.enqueue(message_);
+ adsp_event.Set();
+}
+
+RenderMessage AudioRenderer_Mailbox::HostWaitMessage() {
+ host_event.Wait();
+ RenderMessage msg{RenderMessage::Invalid};
+ if (!host_messages.try_dequeue(msg)) {
+ LOG_ERROR(Service_Audio, "Failed to dequeue host message!");
+ }
+ return msg;
+}
+
+void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) {
+ host_messages.enqueue(message_);
+ host_event.Set();
+}
+
+RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() {
+ adsp_event.Wait();
+ RenderMessage msg{RenderMessage::Invalid};
+ if (!adsp_messages.try_dequeue(msg)) {
+ LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!");
+ }
+ return msg;
+}
+
+CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const u32 session_id) {
+ return command_buffers[session_id];
+}
+
+void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) {
+ command_buffers[session_id] = buffer;
+}
+
+u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const {
+ return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken;
+}
+
+u64 AudioRenderer_Mailbox::GetSignalledTick() const {
+ return signalled_tick;
+}
+
+void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) {
+ signalled_tick = tick;
+}
+
+void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) {
+ command_buffers[session_id].remaining_command_count = 0;
+}
+
+u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const {
+ return command_buffers[session_id].remaining_command_count;
+}
+
+void AudioRenderer_Mailbox::ClearCommandBuffers() {
+ command_buffers[0].buffer = 0;
+ command_buffers[0].size = 0;
+ command_buffers[0].reset_buffers = false;
+ command_buffers[1].buffer = 0;
+ command_buffers[1].size = 0;
+ command_buffers[1].reset_buffers = false;
+}
+
+AudioRenderer::AudioRenderer(Core::System& system_)
+ : system{system_}, sink{system.AudioCore().GetOutputSink()} {
+ CreateSinkStreams();
+}
+
+AudioRenderer::~AudioRenderer() {
+ Stop();
+ for (auto& stream : streams) {
+ if (stream) {
+ sink.CloseStream(stream);
+ }
+ stream = nullptr;
+ }
+}
+
+void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
+ if (running) {
+ return;
+ }
+
+ mailbox = mailbox_;
+ thread = std::thread(&AudioRenderer::ThreadFunc, this);
+ running = true;
+}
+
+void AudioRenderer::Stop() {
+ if (!running) {
+ return;
+ }
+
+ for (auto& stream : streams) {
+ stream->Stop();
+ }
+ thread.join();
+ running = false;
+}
+
+void AudioRenderer::CreateSinkStreams() {
+ u32 channels{sink.GetDeviceChannels()};
+ for (u32 i = 0; i < MaxRendererSessions; i++) {
+ std::string name{fmt::format("ADSP_RenderStream-{}", i)};
+ streams[i] =
+ sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
+ streams[i]->SetRingSize(4);
+ }
+}
+
+void AudioRenderer::ThreadFunc() {
+ constexpr char name[]{"AudioRenderer"};
+ MicroProfileOnThreadCreate(name);
+ Common::SetCurrentThreadName(name);
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
+ if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
+ LOG_ERROR(Service_Audio,
+ "ADSP Audio Renderer -- Failed to receive initialize message from host!");
+ return;
+ }
+
+ mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);
+
+ constexpr u64 max_process_time{2'304'000ULL};
+
+ while (true) {
+ auto message{mailbox->ADSPWaitMessage()};
+ switch (message) {
+ case RenderMessage::AudioRenderer_Shutdown:
+ mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown);
+ return;
+
+ case RenderMessage::AudioRenderer_Render: {
+ std::array<bool, MaxRendererSessions> buffers_reset{};
+ std::array<u64, MaxRendererSessions> render_times_taken{};
+ const auto start_time{system.CoreTiming().GetClockTicks()};
+
+ for (u32 index = 0; index < 2; index++) {
+ auto& command_buffer{mailbox->GetCommandBuffer(index)};
+ auto& command_list_processor{command_list_processors[index]};
+
+ // Check this buffer is valid, as it may not be used.
+ if (command_buffer.buffer != 0) {
+ // If there are no remaining commands (from the previous list),
+ // this is a new command list, initalize it.
+ if (command_buffer.remaining_command_count == 0) {
+ command_list_processor.Initialize(system, command_buffer.buffer,
+ command_buffer.size, streams[index]);
+ }
+
+ if (command_buffer.reset_buffers && !buffers_reset[index]) {
+ streams[index]->ClearQueue();
+ buffers_reset[index] = true;
+ }
+
+ u64 max_time{max_process_time};
+ if (index == 1 && command_buffer.applet_resource_user_id ==
+ mailbox->GetCommandBuffer(0).applet_resource_user_id) {
+ max_time = max_process_time -
+ Core::Timing::CyclesToNs(render_times_taken[0]).count();
+ if (render_times_taken[0] > max_process_time) {
+ max_time = 0;
+ }
+ }
+
+ max_time = std::min(command_buffer.time_limit, max_time);
+ command_list_processor.SetProcessTimeMax(max_time);
+
+ // Process the command list
+ {
+ MICROPROFILE_SCOPE(Audio_Renderer);
+ render_times_taken[index] =
+ command_list_processor.Process(index) - start_time;
+ }
+
+ const auto end_time{system.CoreTiming().GetClockTicks()};
+
+ command_buffer.remaining_command_count =
+ command_list_processor.GetRemainingCommandCount();
+ command_buffer.render_time_taken = end_time - start_time;
+ }
+ }
+
+ mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse);
+ } break;
+
+ default:
+ LOG_WARNING(Service_Audio,
+ "ADSP AudioRenderer received an invalid message, msg={:02X}!",
+ static_cast<u32>(message));
+ break;
+ }
+ }
+}
+
+} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h
new file mode 100644
index 000000000..151f38c1b
--- /dev/null
+++ b/src/audio_core/renderer/adsp/audio_renderer.h
@@ -0,0 +1,203 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <thread>
+
+#include "audio_core/renderer/adsp/command_buffer.h"
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "common/common_types.h"
+#include "common/reader_writer_queue.h"
+#include "common/thread.h"
+
+namespace Core {
+namespace Timing {
+struct EventType;
+}
+class System;
+} // namespace Core
+
+namespace AudioCore {
+namespace Sink {
+class Sink;
+}
+
+namespace AudioRenderer::ADSP {
+
+enum class RenderMessage {
+ /* 0x00 */ Invalid,
+ /* 0x01 */ AudioRenderer_MapUnmap_Map,
+ /* 0x02 */ AudioRenderer_MapUnmap_MapResponse,
+ /* 0x03 */ AudioRenderer_MapUnmap_Unmap,
+ /* 0x04 */ AudioRenderer_MapUnmap_UnmapResponse,
+ /* 0x05 */ AudioRenderer_MapUnmap_InvalidateCache,
+ /* 0x06 */ AudioRenderer_MapUnmap_InvalidateCacheResponse,
+ /* 0x07 */ AudioRenderer_MapUnmap_Shutdown,
+ /* 0x08 */ AudioRenderer_MapUnmap_ShutdownResponse,
+ /* 0x16 */ AudioRenderer_InitializeOK = 0x16,
+ /* 0x20 */ AudioRenderer_RenderResponse = 0x20,
+ /* 0x2A */ AudioRenderer_Render = 0x2A,
+ /* 0x34 */ AudioRenderer_Shutdown = 0x34,
+};
+
+/**
+ * A mailbox for the AudioRenderer, allowing communication between the host and the AudioRenderer
+ * running on the ADSP.
+ */
+class AudioRenderer_Mailbox {
+public:
+ /**
+ * Send a message from the host to the AudioRenderer.
+ *
+ * @param message - The message to send to the AudioRenderer.
+ */
+ void HostSendMessage(RenderMessage message);
+
+ /**
+ * Host wait for a message from the AudioRenderer.
+ *
+ * @return The message returned from the AudioRenderer.
+ */
+ RenderMessage HostWaitMessage();
+
+ /**
+ * Send a message from the AudioRenderer to the host.
+ *
+ * @param message - The message to send to the host.
+ */
+ void ADSPSendMessage(RenderMessage message);
+
+ /**
+ * AudioRenderer wait for a message from the host.
+ *
+ * @return The message returned from the AudioRenderer.
+ */
+ RenderMessage ADSPWaitMessage();
+
+ /**
+ * Get the command buffer with the given session id (0 or 1).
+ *
+ * @param session_id - The session id to get (0 or 1).
+ * @return The command buffer.
+ */
+ CommandBuffer& GetCommandBuffer(u32 session_id);
+
+ /**
+ * Set the command buffer with the given session id (0 or 1).
+ *
+ * @param session_id - The session id to get (0 or 1).
+ * @param buffer - The command buffer to set.
+ */
+ void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer);
+
+ /**
+ * Get the total render time taken for the last command lists sent.
+ *
+ * @return Total render time taken for the last command lists.
+ */
+ u64 GetRenderTimeTaken() const;
+
+ /**
+ * Get the tick the AudioRenderer was signalled.
+ *
+ * @return The tick the AudioRenderer was signalled.
+ */
+ u64 GetSignalledTick() const;
+
+ /**
+ * Set the tick the AudioRenderer was signalled.
+ *
+ * @param tick - The tick the AudioRenderer was signalled.
+ */
+ void SetSignalledTick(u64 tick);
+
+ /**
+ * Clear the remaining command count.
+ *
+ * @param session_id - Index for which command list to clear (0 or 1).
+ */
+ void ClearRemainCount(u32 session_id);
+
+ /**
+ * Get the remaining command count for a given command list.
+ *
+ * @param session_id - Index for which command list to clear (0 or 1).
+ * @return The remaining command count.
+ */
+ u32 GetRemainCommandCount(u32 session_id) const;
+
+ /**
+ * Clear the command buffers (does not clear the time taken or the remaining command count).
+ */
+ void ClearCommandBuffers();
+
+private:
+ /// Host signalling event
+ Common::Event host_event{};
+ /// AudioRenderer signalling event
+ Common::Event adsp_event{};
+ /// Host message queue
+
+ Common::ReaderWriterQueue<RenderMessage> host_messages{};
+ /// AudioRenderer message queue
+
+ Common::ReaderWriterQueue<RenderMessage> adsp_messages{};
+ /// Command buffers
+
+ std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
+ /// Tick the AudioRnederer was signalled
+ u64 signalled_tick{};
+};
+
+/**
+ * The AudioRenderer application running on the ADSP.
+ */
+class AudioRenderer {
+public:
+ explicit AudioRenderer(Core::System& system);
+ ~AudioRenderer();
+
+ /**
+ * Start the AudioRenderer.
+ *
+ * @param mailbox The mailbox to use for this session.
+ */
+ void Start(AudioRenderer_Mailbox* mailbox);
+
+ /**
+ * Stop the AudioRenderer.
+ */
+ void Stop();
+
+private:
+ /**
+ * Main AudioRenderer thread, responsible for processing the command lists.
+ */
+ void ThreadFunc();
+
+ /**
+ * Creates the streams which will receive the processed samples.
+ */
+ void CreateSinkStreams();
+
+ /// Core system
+ Core::System& system;
+ /// Main thread
+ std::thread thread{};
+ /// The current state
+ std::atomic<bool> running{};
+ /// The active mailbox
+ AudioRenderer_Mailbox* mailbox{};
+ /// The command lists to process
+ std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
+ /// The output sink the AudioRenderer will use
+ Sink::Sink& sink;
+ /// The streams which will receive the processed samples
+ std::array<Sink::SinkStream*, MaxRendererSessions> streams;
+};
+
+} // namespace AudioRenderer::ADSP
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/adsp/command_buffer.h b/src/audio_core/renderer/adsp/command_buffer.h
new file mode 100644
index 000000000..880b279d8
--- /dev/null
+++ b/src/audio_core/renderer/adsp/command_buffer.h
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer::ADSP {
+
+struct CommandBuffer {
+ CpuAddr buffer;
+ u64 size;
+ u64 time_limit;
+ u32 remaining_command_count;
+ bool reset_buffers;
+ u64 applet_resource_user_id;
+ u64 render_time_taken;
+};
+
+} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/command_list_processor.cpp b/src/audio_core/renderer/adsp/command_list_processor.cpp
new file mode 100644
index 000000000..e3bf2d7ec
--- /dev/null
+++ b/src/audio_core/renderer/adsp/command_list_processor.cpp
@@ -0,0 +1,109 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <string>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/command_list_header.h"
+#include "audio_core/renderer/command/commands.h"
+#include "common/settings.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer::ADSP {
+
+void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size,
+ Sink::SinkStream* stream_) {
+ system = &system_;
+ memory = &system->Memory();
+ stream = stream_;
+ header = reinterpret_cast<CommandListHeader*>(buffer);
+ commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
+ commands_buffer_size = size;
+ command_count = header->command_count;
+ sample_count = header->sample_count;
+ target_sample_rate = header->sample_rate;
+ mix_buffers = header->samples_buffer;
+ buffer_count = header->buffer_count;
+ processed_command_count = 0;
+}
+
+void CommandListProcessor::SetProcessTimeMax(const u64 time) {
+ max_process_time = time;
+}
+
+u32 CommandListProcessor::GetRemainingCommandCount() const {
+ return command_count - processed_command_count;
+}
+
+void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
+ commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
+ commands_buffer_size = size;
+}
+
+Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
+ return stream;
+}
+
+u64 CommandListProcessor::Process(u32 session_id) {
+ const auto start_time_{system->CoreTiming().GetClockTicks()};
+ const auto command_base{CpuAddr(commands)};
+
+ if (processed_command_count > 0) {
+ current_processing_time += start_time_ - end_time;
+ } else {
+ start_time = start_time_;
+ current_processing_time = 0;
+ }
+
+ std::string dump{fmt::format("\nSession {}\n", session_id)};
+
+ for (u32 index = 0; index < command_count; index++) {
+ auto& command{*reinterpret_cast<ICommand*>(commands)};
+
+ if (command.magic != 0xCAFEBABE) {
+ LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}",
+ command.magic);
+ return system->CoreTiming().GetClockTicks() - start_time_;
+ }
+
+ auto current_offset{CpuAddr(commands) - command_base};
+
+ if (current_offset + command.size > commands_buffer_size) {
+ LOG_ERROR(Service_Audio,
+ "Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}",
+ commands_buffer_size,
+ CpuAddr(commands) + command.size - sizeof(CommandListHeader));
+ return system->CoreTiming().GetClockTicks() - start_time_;
+ }
+
+ if (Settings::values.dump_audio_commands) {
+ command.Dump(*this, dump);
+ }
+
+ if (!command.Verify(*this)) {
+ break;
+ }
+
+ if (command.enabled) {
+ command.Process(*this);
+ } else {
+ dump += fmt::format("\tDisabled!\n");
+ }
+
+ processed_command_count++;
+ commands += command.size;
+ }
+
+ if (Settings::values.dump_audio_commands && dump != last_dump) {
+ LOG_WARNING(Service_Audio, "{}", dump);
+ last_dump = dump;
+ }
+
+ end_time = system->CoreTiming().GetClockTicks();
+ return end_time - start_time_;
+}
+
+} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/renderer/adsp/command_list_processor.h
new file mode 100644
index 000000000..d78269e1d
--- /dev/null
+++ b/src/audio_core/renderer/adsp/command_list_processor.h
@@ -0,0 +1,119 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace Core {
+namespace Memory {
+class Memory;
+}
+class System;
+} // namespace Core
+
+namespace AudioCore {
+namespace Sink {
+class SinkStream;
+}
+
+namespace AudioRenderer {
+struct CommandListHeader;
+
+namespace ADSP {
+
+/**
+ * A processor for command lists given to the AudioRenderer.
+ */
+class CommandListProcessor {
+public:
+ /**
+ * Initialize the processor.
+ *
+ * @param system - The core system.
+ * @param buffer - The command buffer to process.
+ * @param size - The size of the buffer.
+ * @param stream - The stream to be used for sending the samples.
+ */
+ void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream);
+
+ /**
+ * Set the maximum processing time for this command list.
+ *
+ * @param time - The maximum process time.
+ */
+ void SetProcessTimeMax(u64 time);
+
+ /**
+ * Get the remaining command count for this list.
+ *
+ * @return The remaining command count.
+ */
+ u32 GetRemainingCommandCount() const;
+
+ /**
+ * Set the command buffer.
+ *
+ * @param buffer - The buffer to use.
+ * @param size - The size of the buffer.
+ */
+ void SetBuffer(CpuAddr buffer, u64 size);
+
+ /**
+ * Get the stream for this command list.
+ *
+ * @return The stream associated with this command list.
+ */
+ Sink::SinkStream* GetOutputSinkStream() const;
+
+ /**
+ * Process the command list.
+ *
+ * @param session_id - Session ID for the commands being processed.
+ *
+ * @return The time taken to process.
+ */
+ u64 Process(u32 session_id);
+
+ /// Core system
+ Core::System* system{};
+ /// Core memory
+ Core::Memory::Memory* memory{};
+ /// Stream for the processed samples
+ Sink::SinkStream* stream{};
+ /// Header info for this command list
+ CommandListHeader* header{};
+ /// The command buffer
+ u8* commands{};
+ /// The command buffer size
+ u64 commands_buffer_size{};
+ /// The maximum processing time allotted
+ u64 max_process_time{};
+ /// The number of commands in the buffer
+ u32 command_count{};
+ /// The target sample count for output
+ u32 sample_count{};
+ /// The target sample rate for output
+ u32 target_sample_rate{};
+ /// The mixing buffers used by the commands
+ std::span<s32> mix_buffers{};
+ /// The number of mix buffers
+ u32 buffer_count{};
+ /// The number of processed commands so far
+ u32 processed_command_count{};
+ /// The processing start time of this list
+ u64 start_time{};
+ /// The current processing time for this list
+ u64 current_processing_time{};
+ /// The end processing time for this list
+ u64 end_time{};
+ /// Last command list string generated, used for dumping audio commands to console
+ std::string last_dump{};
+};
+
+} // namespace ADSP
+} // namespace AudioRenderer
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp
new file mode 100644
index 000000000..0d9d8f6ce
--- /dev/null
+++ b/src/audio_core/renderer/audio_device.cpp
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <span>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/common/feature_support.h"
+#include "audio_core/renderer/audio_device.h"
+#include "audio_core/sink/sink.h"
+#include "core/core.h"
+
+namespace AudioCore::AudioRenderer {
+
+constexpr std::array usb_device_names{
+ AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
+ AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
+ AudioDevice::AudioDeviceName{"AudioTvOutput"},
+ AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"},
+};
+
+constexpr std::array device_names{
+ AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
+ AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
+ AudioDevice::AudioDeviceName{"AudioTvOutput"},
+};
+
+constexpr std::array output_device_names{
+ AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
+ AudioDevice::AudioDeviceName{"AudioTvOutput"},
+ AudioDevice::AudioDeviceName{"AudioExternalOutput"},
+};
+
+AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_,
+ const u32 revision)
+ : output_sink{system.AudioCore().GetOutputSink()},
+ applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {}
+
+u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
+ const size_t max_count) const {
+ std::span<const AudioDeviceName> names{};
+
+ if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
+ names = usb_device_names;
+ } else {
+ names = device_names;
+ }
+
+ const u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
+ for (u32 i = 0; i < out_count; i++) {
+ out_buffer.push_back(names[i]);
+ }
+ return out_count;
+}
+
+u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer,
+ const size_t max_count) const {
+ const u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
+
+ for (u32 i = 0; i < out_count; i++) {
+ out_buffer.push_back(output_device_names[i]);
+ }
+ return out_count;
+}
+
+void AudioDevice::SetDeviceVolumes(const f32 volume) {
+ output_sink.SetDeviceVolume(volume);
+}
+
+f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const {
+ return output_sink.GetDeviceVolume();
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h
new file mode 100644
index 000000000..dd6be70ee
--- /dev/null
+++ b/src/audio_core/renderer/audio_device.h
@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string_view>
+
+#include "audio_core/audio_render_manager.h"
+
+namespace Core {
+class System;
+}
+
+namespace AudioCore {
+namespace Sink {
+class Sink;
+}
+
+namespace AudioRenderer {
+/**
+ * An interface to an output audio device available to the Switch.
+ */
+class AudioDevice {
+public:
+ struct AudioDeviceName {
+ std::array<char, 0x100> name{};
+
+ constexpr AudioDeviceName(std::string_view name_) {
+ name_.copy(name.data(), name.size() - 1);
+ }
+ };
+
+ explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision);
+
+ /**
+ * Get a list of the available output devices.
+ *
+ * @param out_buffer - Output buffer to write the available device names.
+ * @param max_count - Maximum number of devices to write (count of out_buffer).
+ * @return Number of device names written.
+ */
+ u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
+
+ /**
+ * Get a list of the available output devices.
+ * Different to above somehow...
+ *
+ * @param out_buffer - Output buffer to write the available device names.
+ * @param max_count - Maximum number of devices to write (count of out_buffer).
+ * @return Number of device names written.
+ */
+ u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
+
+ /**
+ * Set the volume of all streams in the backend sink.
+ *
+ * @param volume - Volume to set.
+ */
+ void SetDeviceVolumes(f32 volume);
+
+ /**
+ * Get the volume for a given device name.
+ * Note: This is not fully implemented, we only assume 1 device for all streams.
+ *
+ * @param name - Name of the device to check. Unused.
+ * @return Volume of the device.
+ */
+ f32 GetDeviceVolume(std::string_view name) const;
+
+private:
+ /// Backend output sink for the device
+ Sink::Sink& output_sink;
+ /// Resource id this device is used for
+ const u64 applet_resource_user_id;
+ /// User audio renderer revision
+ const u32 user_revision;
+};
+
+} // namespace AudioRenderer
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/audio_renderer.cpp b/src/audio_core/renderer/audio_renderer.cpp
new file mode 100644
index 000000000..51aa17599
--- /dev/null
+++ b/src/audio_core/renderer/audio_renderer.cpp
@@ -0,0 +1,67 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/audio_render_manager.h"
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/renderer/audio_renderer.h"
+#include "audio_core/renderer/system_manager.h"
+#include "core/core.h"
+#include "core/hle/kernel/k_transfer_memory.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace AudioCore::AudioRenderer {
+
+Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event)
+ : core{system_}, manager{manager_}, system{system_, rendered_event} {}
+
+Result Renderer::Initialize(const AudioRendererParameterInternal& params,
+ Kernel::KTransferMemory* transfer_memory,
+ const u64 transfer_memory_size, const u32 process_handle,
+ const u64 applet_resource_user_id, const s32 session_id) {
+ if (params.execution_mode == ExecutionMode::Auto) {
+ if (!manager.AddSystem(system)) {
+ LOG_ERROR(Service_Audio,
+ "Both Audio Render sessions are in use, cannot create any more");
+ return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED;
+ }
+ system_registered = true;
+ }
+
+ initialized = true;
+ system.Initialize(params, transfer_memory, transfer_memory_size, process_handle,
+ applet_resource_user_id, session_id);
+
+ return ResultSuccess;
+}
+
+void Renderer::Finalize() {
+ auto session_id{system.GetSessionId()};
+
+ system.Finalize();
+
+ if (system_registered) {
+ manager.RemoveSystem(system);
+ system_registered = false;
+ }
+
+ manager.ReleaseSessionId(session_id);
+}
+
+System& Renderer::GetSystem() {
+ return system;
+}
+
+void Renderer::Start() {
+ system.Start();
+}
+
+void Renderer::Stop() {
+ system.Stop();
+}
+
+Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performance,
+ std::span<u8> output) {
+ return system.Update(input, performance, output);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/audio_renderer.h b/src/audio_core/renderer/audio_renderer.h
new file mode 100644
index 000000000..90c6f9727
--- /dev/null
+++ b/src/audio_core/renderer/audio_renderer.h
@@ -0,0 +1,97 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/system.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+class KTransferMemory;
+}
+
+namespace AudioCore {
+struct AudioRendererParameterInternal;
+
+namespace AudioRenderer {
+class Manager;
+
+/**
+ * Audio Renderer, wraps the main audio system and is mainly responsible for handling service calls.
+ */
+class Renderer {
+public:
+ explicit Renderer(Core::System& system, Manager& manager, Kernel::KEvent* rendered_event);
+
+ /**
+ * Initialize the renderer.
+ * Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes
+ * everything to a default state.
+ *
+ * @param params - Input parameters to initialize the system with.
+ * @param transfer_memory - Game-supplied memory for all workbuffers. Unused.
+ * @param transfer_memory_size - Size of the transfer memory. Unused.
+ * @param process_handle - Process handle, also used for memory. Unused.
+ * @param applet_resource_user_id - Applet id for this renderer. Unused.
+ * @param session_id - Session id of this renderer.
+ * @return Result code.
+ */
+ Result Initialize(const AudioRendererParameterInternal& params,
+ Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
+ u32 process_handle, u64 applet_resource_user_id, s32 session_id);
+
+ /**
+ * Finalize the renderer for shutdown.
+ */
+ void Finalize();
+
+ /**
+ * Get the renderer's system.
+ *
+ * @return Reference to the system.
+ */
+ System& GetSystem();
+
+ /**
+ * Start the renderer.
+ */
+ void Start();
+
+ /**
+ * Stop the renderer.
+ */
+ void Stop();
+
+ /**
+ * Update the audio renderer with new information.
+ * Called via RequestUpdate from the AudRen:U service.
+ *
+ * @param input - Input buffer containing the new data.
+ * @param performance - Optional performance buffer for outputting performance metrics.
+ * @param output - Output data from the renderer.
+ * @return Result code.
+ */
+ Result RequestUpdate(std::span<const u8> input, std::span<u8> performance,
+ std::span<u8> output);
+
+private:
+ /// System core
+ Core::System& core;
+ /// Manager this renderer is registered with
+ Manager& manager;
+ /// Is the audio renderer initialized?
+ bool initialized{};
+ /// Is the system registered with the manager?
+ bool system_registered{};
+ /// Audio render system, main driver of audio rendering
+ System system;
+};
+
+} // namespace AudioRenderer
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp
new file mode 100644
index 000000000..3d2a91312
--- /dev/null
+++ b/src/audio_core/renderer/behavior/behavior_info.cpp
@@ -0,0 +1,193 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/common/feature_support.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+
+namespace AudioCore::AudioRenderer {
+
+BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {}
+
+u32 BehaviorInfo::GetProcessRevisionNum() const {
+ return process_revision;
+}
+
+u32 BehaviorInfo::GetProcessRevision() const {
+ return Common::MakeMagic('R', 'E', 'V',
+ static_cast<char>(static_cast<u8>('0') + process_revision));
+}
+
+u32 BehaviorInfo::GetUserRevisionNum() const {
+ return user_revision;
+}
+
+u32 BehaviorInfo::GetUserRevision() const {
+ return Common::MakeMagic('R', 'E', 'V',
+ static_cast<char>(static_cast<u8>('0') + user_revision));
+}
+
+void BehaviorInfo::SetUserLibRevision(const u32 user_revision_) {
+ user_revision = GetRevisionNum(user_revision_);
+}
+
+void BehaviorInfo::ClearError() {
+ error_count = 0;
+}
+
+void BehaviorInfo::AppendError(const ErrorInfo& error) {
+ LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}",
+ error.error_code.raw, error.address);
+ if (error_count < MaxErrors) {
+ errors[error_count++] = error;
+ }
+}
+
+void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const {
+ out_count = std::min(error_count, MaxErrors);
+
+ for (size_t i = 0; i < MaxErrors; i++) {
+ if (i < out_count) {
+ out_errors[i] = errors[i];
+ } else {
+ out_errors[i] = {};
+ }
+ }
+}
+
+void BehaviorInfo::UpdateFlags(const Flags flags_) {
+ flags = flags_;
+}
+
+bool BehaviorInfo::IsMemoryForceMappingEnabled() const {
+ return flags.IsMemoryForceMappingEnabled;
+}
+
+bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
+ return CheckFeatureSupported(SupportTags::AdpcmLoopContextBugFix, user_revision);
+}
+
+bool BehaviorInfo::IsSplitterSupported() const {
+ return CheckFeatureSupported(SupportTags::Splitter, user_revision);
+}
+
+bool BehaviorInfo::IsSplitterBugFixed() const {
+ return CheckFeatureSupported(SupportTags::SplitterBugFix, user_revision);
+}
+
+bool BehaviorInfo::IsEffectInfoVersion2Supported() const {
+ return CheckFeatureSupported(SupportTags::EffectInfoVer2, user_revision);
+}
+
+bool BehaviorInfo::IsVariadicCommandBufferSizeSupported() const {
+ return CheckFeatureSupported(SupportTags::AudioRendererVariadicCommandBufferSize,
+ user_revision);
+}
+
+bool BehaviorInfo::IsWaveBufferVer2Supported() const {
+ return CheckFeatureSupported(SupportTags::WaveBufferVer2, user_revision);
+}
+
+bool BehaviorInfo::IsLongSizePreDelaySupported() const {
+ return CheckFeatureSupported(SupportTags::LongSizePreDelay, user_revision);
+}
+
+bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion2Supported() const {
+ return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion2,
+ user_revision);
+}
+
+bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion3Supported() const {
+ return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion3,
+ user_revision);
+}
+
+bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion4Supported() const {
+ return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
+ user_revision);
+}
+
+bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion5Supported() const {
+ return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
+ user_revision);
+}
+
+bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const {
+ return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit70Percent,
+ user_revision);
+}
+
+bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const {
+ return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit75Percent,
+ user_revision);
+}
+
+bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const {
+ return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit80Percent,
+ user_revision);
+}
+
+bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
+ return CheckFeatureSupported(SupportTags::FlushVoiceWaveBuffers, user_revision);
+}
+
+bool BehaviorInfo::IsElapsedFrameCountSupported() const {
+ return CheckFeatureSupported(SupportTags::ElapsedFrameCount, user_revision);
+}
+
+bool BehaviorInfo::IsPerformanceMetricsDataFormatVersion2Supported() const {
+ return CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision);
+}
+
+size_t BehaviorInfo::GetPerformanceMetricsDataFormat() const {
+ if (CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision)) {
+ return 2;
+ }
+ return 1;
+}
+
+bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
+ return CheckFeatureSupported(SupportTags::VoicePitchAndSrcSkipped, user_revision);
+}
+
+bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
+ return CheckFeatureSupported(SupportTags::VoicePlayedSampleCountResetAtLoopPoint,
+ user_revision);
+}
+
+bool BehaviorInfo::IsBiquadFilterEffectStateClearBugFixed() const {
+ return CheckFeatureSupported(SupportTags::BiquadFilterEffectStateClearBugFix, user_revision);
+}
+
+bool BehaviorInfo::IsVolumeMixParameterPrecisionQ23Supported() const {
+ return CheckFeatureSupported(SupportTags::VolumeMixParameterPrecisionQ23, user_revision);
+}
+
+bool BehaviorInfo::UseBiquadFilterFloatProcessing() const {
+ return CheckFeatureSupported(SupportTags::BiquadFilterFloatProcessing, user_revision);
+}
+
+bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
+ return CheckFeatureSupported(SupportTags::MixInParameterDirtyOnlyUpdate, user_revision);
+}
+
+bool BehaviorInfo::UseMultiTapBiquadFilterProcessing() const {
+ return CheckFeatureSupported(SupportTags::MultiTapBiquadFilterProcessing, user_revision);
+}
+
+bool BehaviorInfo::IsDeviceApiVersion2Supported() const {
+ return CheckFeatureSupported(SupportTags::DeviceApiVersion2, user_revision);
+}
+
+bool BehaviorInfo::IsDelayChannelMappingChanged() const {
+ return CheckFeatureSupported(SupportTags::DelayChannelMappingChange, user_revision);
+}
+
+bool BehaviorInfo::IsReverbChannelMappingChanged() const {
+ return CheckFeatureSupported(SupportTags::ReverbChannelMappingChange, user_revision);
+}
+
+bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const {
+ return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h
new file mode 100644
index 000000000..15c948344
--- /dev/null
+++ b/src/audio_core/renderer/behavior/behavior_info.h
@@ -0,0 +1,376 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Holds host and user revisions, checks whether render features can be enabled, and reports errors.
+ */
+class BehaviorInfo {
+ static constexpr u32 MaxErrors = 10;
+
+public:
+ struct ErrorInfo {
+ /* 0x00 */ Result error_code{0};
+ /* 0x04 */ u32 unk_04;
+ /* 0x08 */ CpuAddr address;
+ };
+ static_assert(sizeof(ErrorInfo) == 0x10, "BehaviorInfo::ErrorInfo has the wrong size!");
+
+ struct Flags {
+ u64 IsMemoryForceMappingEnabled : 1;
+ };
+
+ struct InParameter {
+ /* 0x00 */ u32 revision;
+ /* 0x08 */ Flags flags;
+ };
+ static_assert(sizeof(InParameter) == 0x10, "BehaviorInfo::InParameter has the wrong size!");
+
+ struct OutStatus {
+ /* 0x00 */ std::array<ErrorInfo, MaxErrors> errors;
+ /* 0xA0 */ u32 error_count;
+ /* 0xA4 */ char unkA4[0xC];
+ };
+ static_assert(sizeof(OutStatus) == 0xB0, "BehaviorInfo::OutStatus has the wrong size!");
+
+ BehaviorInfo();
+
+ /**
+ * Get the host revision as a number.
+ *
+ * @return The host revision.
+ */
+ u32 GetProcessRevisionNum() const;
+
+ /**
+ * Get the host revision in chars, e.g REV8.
+ * Rev 10 and higher use the ascii characters above 9.
+ * E.g:
+ * Rev 10 = REV:
+ * Rev 11 = REV;
+ *
+ * @return The host revision.
+ */
+ u32 GetProcessRevision() const;
+
+ /**
+ * Get the user revision as a number.
+ *
+ * @return The user revision.
+ */
+ u32 GetUserRevisionNum() const;
+
+ /**
+ * Get the user revision in chars, e.g REV8.
+ * Rev 10 and higher use the ascii characters above 9. REV: REV; etc.
+ *
+ * @return The user revision.
+ */
+ u32 GetUserRevision() const;
+
+ /**
+ * Set the user revision.
+ *
+ * @param user_revision - The user's revision.
+ */
+ void SetUserLibRevision(u32 user_revision);
+
+ /**
+ * Clear the current error count.
+ */
+ void ClearError();
+
+ /**
+ * Append an error to the error list.
+ *
+ * @param error - The new error.
+ */
+ void AppendError(const ErrorInfo& error);
+
+ /**
+ * Copy errors to the given output container.
+ *
+ * @param out_errors - Output container to receive the errors.
+ * @param out_count - The number of errors written.
+ */
+ void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const;
+
+ /**
+ * Update the behaviour flags.
+ *
+ * @param flags - New flags to use.
+ */
+ void UpdateFlags(Flags flags);
+
+ /**
+ * Check if memory pools can be forcibly mapped.
+ *
+ * @return True if enabled, otherwise false.
+ */
+ bool IsMemoryForceMappingEnabled() const;
+
+ /**
+ * Check if the ADPCM context bug is fixed.
+ * The ADPCM context was not being sent to the AudioRenderer, leading to incorrect scaling being
+ * used.
+ *
+ * @return True if fixed, otherwise false.
+ */
+ bool IsAdpcmLoopContextBugFixed() const;
+
+ /**
+ * Check if the splitter is supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsSplitterSupported() const;
+
+ /**
+ * Check if the splitter bug is fixed.
+ * Update is given the wrong number of splitter destinations, leading to invalid data
+ * being processed.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsSplitterBugFixed() const;
+
+ /**
+ * Check if effects version 2 are supported.
+ * This gives support for returning effect states from the AudioRenderer, currently only used
+ * for Limiter statistics.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsEffectInfoVersion2Supported() const;
+
+ /**
+ * Check if a variadic command buffer is supported.
+ * As of Rev 5 with the added optional performance metric logging, the command
+ * buffer can be a variable size, so take that into account for calcualting its size.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsVariadicCommandBufferSizeSupported() const;
+
+ /**
+ * Check if wave buffers version 2 are supported.
+ * See WaveBufferVersion1 and WaveBufferVersion2.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsWaveBufferVer2Supported() const;
+
+ /**
+ * Check if long size pre delay is supported.
+ * This allows a longer initial delay time for the Reverb command.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsLongSizePreDelaySupported() const;
+
+ /**
+ * Check if the command time estimator version 2 is supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsCommandProcessingTimeEstimatorVersion2Supported() const;
+
+ /**
+ * Check if the command time estimator version 3 is supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsCommandProcessingTimeEstimatorVersion3Supported() const;
+
+ /**
+ * Check if the command time estimator version 4 is supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsCommandProcessingTimeEstimatorVersion4Supported() const;
+
+ /**
+ * Check if the command time estimator version 5 is supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsCommandProcessingTimeEstimatorVersion5Supported() const;
+
+ /**
+ * Check if the AudioRenderer can use up to 70% of the allocated processing timeslice.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsAudioRendererProcessingTimeLimit70PercentSupported() const;
+
+ /**
+ * Check if the AudioRenderer can use up to 75% of the allocated processing timeslice.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsAudioRendererProcessingTimeLimit75PercentSupported() const;
+
+ /**
+ * Check if the AudioRenderer can use up to 80% of the allocated processing timeslice.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsAudioRendererProcessingTimeLimit80PercentSupported() const;
+
+ /**
+ * Check if voice flushing is supported
+ * This allowws low-priority voices to be dropped if the AudioRenderer is running behind.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsFlushVoiceWaveBuffersSupported() const;
+
+ /**
+ * Check if counting the number of elapsed frames is supported.
+ * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
+ * processed a command list.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsElapsedFrameCountSupported() const;
+
+ /**
+ * Check if performance metrics version 2 are supported.
+ * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
+ * (Unused?).
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsPerformanceMetricsDataFormatVersion2Supported() const;
+
+ /**
+ * Get the supported performance metrics version.
+ * Version 2 logs some extra fields in output, such as number of voices dropped,
+ * processing start time, if the AudioRenderer exceeded its time, etc.
+ *
+ * @return Version supported, either 1 or 2.
+ */
+ size_t GetPerformanceMetricsDataFormat() const;
+
+ /**
+ * Check if skipping voice pitch and sample rate conversion is supported.
+ * This speeds up the data source commands by skipping resampling if unwanted.
+ * See AudioCore::AudioRenderer::DecodeFromWaveBuffers
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsVoicePitchAndSrcSkippedSupported() const;
+
+ /**
+ * Check if resetting played sample count at loop points is supported.
+ * This resets the number of samples played in a voice state when a loop point is reached.
+ * See AudioCore::AudioRenderer::DecodeFromWaveBuffers
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
+
+ /**
+ * Check if the clear state bug for biquad filters is fixed.
+ * The biquad state was not marked as needing re-initialisation when the effect was updated, it
+ * was only initialized once with a new effect.
+ *
+ * @return True if fixed, otherwise false.
+ */
+ bool IsBiquadFilterEffectStateClearBugFixed() const;
+
+ /**
+ * Check if Q23 precision is supported for fixed point.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsVolumeMixParameterPrecisionQ23Supported() const;
+
+ /**
+ * Check if float processing for biuad filters is supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool UseBiquadFilterFloatProcessing() const;
+
+ /**
+ * Check if dirty-only mix updates are supported.
+ * This saves a lot of buffer size as mixes can be large and not change much.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsMixInParameterDirtyOnlyUpdateSupported() const;
+
+ /**
+ * Check if multi-tap biquad filters are supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool UseMultiTapBiquadFilterProcessing() const;
+
+ /**
+ * Check if device api version 2 is supported.
+ * In the SDK but not in any sysmodule? Not sure, left here for completeness anyway.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsDeviceApiVersion2Supported() const;
+
+ /**
+ * Check if new channel mappings are used for Delay commands.
+ * Older commands used:
+ * front left/front right/back left/back right/center/lfe
+ * Whereas everywhere else in the code uses:
+ * front left/front right/center/lfe/back left/back right
+ * This corrects that and makes everything standardised.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsDelayChannelMappingChanged() const;
+
+ /**
+ * Check if new channel mappings are used for Reverb commands.
+ * Older commands used:
+ * front left/front right/back left/back right/center/lfe
+ * Whereas everywhere else in the code uses:
+ * front left/front right/center/lfe/back left/back right
+ * This corrects that and makes everything standardised.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsReverbChannelMappingChanged() const;
+
+ /**
+ * Check if new channel mappings are used for I3dl2Reverb commands.
+ * Older commands used:
+ * front left/front right/back left/back right/center/lfe
+ * Whereas everywhere else in the code uses:
+ * front left/front right/center/lfe/back left/back right
+ * This corrects that and makes everything standardised.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsI3dl2ReverbChannelMappingChanged() const;
+
+ /// Host version
+ u32 process_revision;
+ /// User version
+ u32 user_revision{};
+ /// Behaviour flags
+ Flags flags{};
+ /// Errors generated and reported during Update
+ std::array<ErrorInfo, MaxErrors> errors{};
+ /// Error count
+ u32 error_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp
new file mode 100644
index 000000000..c0a307b89
--- /dev/null
+++ b/src/audio_core/renderer/behavior/info_updater.cpp
@@ -0,0 +1,539 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/common/feature_support.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/behavior/info_updater.h"
+#include "audio_core/renderer/effect/effect_context.h"
+#include "audio_core/renderer/effect/effect_reset.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "audio_core/renderer/mix/mix_context.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
+#include "audio_core/renderer/sink/device_sink_info.h"
+#include "audio_core/renderer/sink/sink_context.h"
+#include "audio_core/renderer/splitter/splitter_context.h"
+#include "audio_core/renderer/voice/voice_context.h"
+
+namespace AudioCore::AudioRenderer {
+
+InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_,
+ const u32 process_handle_, BehaviorInfo& behaviour_)
+ : input{input_.data() + sizeof(UpdateDataHeader)},
+ input_origin{input_}, output{output_.data() + sizeof(UpdateDataHeader)},
+ output_origin{output_}, in_header{reinterpret_cast<const UpdateDataHeader*>(
+ input_origin.data())},
+ out_header{reinterpret_cast<UpdateDataHeader*>(output_origin.data())},
+ expected_input_size{input_.size()}, expected_output_size{output_.size()},
+ process_handle{process_handle_}, behaviour{behaviour_} {
+ std::construct_at<UpdateDataHeader>(out_header, behaviour.GetProcessRevision());
+}
+
+Result InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
+ const auto voice_count{voice_context.GetCount()};
+ std::span<const VoiceChannelResource::InParameter> in_params{
+ reinterpret_cast<const VoiceChannelResource::InParameter*>(input), voice_count};
+
+ for (u32 i = 0; i < voice_count; i++) {
+ auto& resource{voice_context.GetChannelResource(i)};
+ resource.in_use = in_params[i].in_use;
+ if (in_params[i].in_use) {
+ resource.mix_volumes = in_params[i].mix_volumes;
+ }
+ }
+
+ const auto consumed_input_size{voice_count *
+ static_cast<u32>(sizeof(VoiceChannelResource::InParameter))};
+ if (consumed_input_size != in_header->voice_resources_size) {
+ LOG_ERROR(Service_Audio,
+ "Consumed an incorrect voice resource size, header size={}, consumed={}",
+ in_header->voice_resources_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += consumed_input_size;
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateVoices(VoiceContext& voice_context,
+ std::span<MemoryPoolInfo> memory_pools,
+ const u32 memory_pool_count) {
+ const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
+ behaviour.IsMemoryForceMappingEnabled());
+ const auto voice_count{voice_context.GetCount()};
+ std::span<const VoiceInfo::InParameter> in_params{
+ reinterpret_cast<const VoiceInfo::InParameter*>(input), voice_count};
+ std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(output),
+ voice_count};
+
+ for (u32 i = 0; i < voice_count; i++) {
+ auto& voice_info{voice_context.GetInfo(i)};
+ voice_info.in_use = false;
+ }
+
+ u32 new_voice_count{0};
+
+ for (u32 i = 0; i < voice_count; i++) {
+ const auto& in_param{in_params[i]};
+ std::array<VoiceState*, MaxChannels> voice_states{};
+
+ if (!in_param.in_use) {
+ continue;
+ }
+
+ auto& voice_info{voice_context.GetInfo(in_param.id)};
+
+ for (u32 channel = 0; channel < in_param.channel_count; channel++) {
+ voice_states[channel] = &voice_context.GetState(in_param.channel_resource_ids[channel]);
+ }
+
+ if (in_param.is_new) {
+ voice_info.Initialize();
+
+ for (u32 channel = 0; channel < in_param.channel_count; channel++) {
+ std::memset(voice_states[channel], 0, sizeof(VoiceState));
+ }
+ }
+
+ BehaviorInfo::ErrorInfo update_error{};
+ voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour);
+
+ if (!update_error.error_code.IsSuccess()) {
+ behaviour.AppendError(update_error);
+ }
+
+ std::array<std::array<BehaviorInfo::ErrorInfo, 2>, MaxWaveBuffers> wavebuffer_errors{};
+ voice_info.UpdateWaveBuffers(wavebuffer_errors, MaxWaveBuffers * 2, in_param, voice_states,
+ pool_mapper, behaviour);
+
+ for (auto& wavebuffer_error : wavebuffer_errors) {
+ for (auto& error : wavebuffer_error) {
+ if (error.error_code.IsError()) {
+ behaviour.AppendError(error);
+ }
+ }
+ }
+
+ voice_info.WriteOutStatus(out_params[i], in_param, voice_states);
+ new_voice_count += in_param.channel_count;
+ }
+
+ auto consumed_input_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::InParameter))};
+ auto consumed_output_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::OutStatus))};
+ if (consumed_input_size != in_header->voices_size) {
+ LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}",
+ in_header->voices_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ out_header->voices_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+ input += consumed_input_size;
+ output += consumed_output_size;
+
+ voice_context.SetActiveCount(new_voice_count);
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateEffects(EffectContext& effect_context, const bool renderer_active,
+ std::span<MemoryPoolInfo> memory_pools,
+ const u32 memory_pool_count) {
+ if (behaviour.IsEffectInfoVersion2Supported()) {
+ return UpdateEffectsVersion2(effect_context, renderer_active, memory_pools,
+ memory_pool_count);
+ } else {
+ return UpdateEffectsVersion1(effect_context, renderer_active, memory_pools,
+ memory_pool_count);
+ }
+}
+
+Result InfoUpdater::UpdateEffectsVersion1(EffectContext& effect_context, const bool renderer_active,
+ std::span<MemoryPoolInfo> memory_pools,
+ const u32 memory_pool_count) {
+ PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
+ behaviour.IsMemoryForceMappingEnabled());
+
+ const auto effect_count{effect_context.GetCount()};
+
+ std::span<const EffectInfoBase::InParameterVersion1> in_params{
+ reinterpret_cast<const EffectInfoBase::InParameterVersion1*>(input), effect_count};
+ std::span<EffectInfoBase::OutStatusVersion1> out_params{
+ reinterpret_cast<EffectInfoBase::OutStatusVersion1*>(output), effect_count};
+
+ for (u32 i = 0; i < effect_count; i++) {
+ auto effect_info{&effect_context.GetInfo(i)};
+ if (effect_info->GetType() != in_params[i].type) {
+ effect_info->ForceUnmapBuffers(pool_mapper);
+ ResetEffect(effect_info, in_params[i].type);
+ }
+
+ BehaviorInfo::ErrorInfo error_info{};
+ effect_info->Update(error_info, in_params[i], pool_mapper);
+ if (error_info.error_code.IsError()) {
+ behaviour.AppendError(error_info);
+ }
+
+ effect_info->StoreStatus(out_params[i], renderer_active);
+ }
+
+ auto consumed_input_size{effect_count *
+ static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion1))};
+ auto consumed_output_size{effect_count *
+ static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion1))};
+ if (consumed_input_size != in_header->effects_size) {
+ LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}",
+ in_header->effects_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ out_header->effects_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+ input += consumed_input_size;
+ output += consumed_output_size;
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateEffectsVersion2(EffectContext& effect_context, const bool renderer_active,
+ std::span<MemoryPoolInfo> memory_pools,
+ const u32 memory_pool_count) {
+ PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
+ behaviour.IsMemoryForceMappingEnabled());
+
+ const auto effect_count{effect_context.GetCount()};
+
+ std::span<const EffectInfoBase::InParameterVersion2> in_params{
+ reinterpret_cast<const EffectInfoBase::InParameterVersion2*>(input), effect_count};
+ std::span<EffectInfoBase::OutStatusVersion2> out_params{
+ reinterpret_cast<EffectInfoBase::OutStatusVersion2*>(output), effect_count};
+
+ for (u32 i = 0; i < effect_count; i++) {
+ auto effect_info{&effect_context.GetInfo(i)};
+ if (effect_info->GetType() != in_params[i].type) {
+ effect_info->ForceUnmapBuffers(pool_mapper);
+ ResetEffect(effect_info, in_params[i].type);
+ }
+
+ BehaviorInfo::ErrorInfo error_info{};
+ effect_info->Update(error_info, in_params[i], pool_mapper);
+
+ if (error_info.error_code.IsError()) {
+ behaviour.AppendError(error_info);
+ }
+
+ effect_info->StoreStatus(out_params[i], renderer_active);
+
+ if (in_params[i].is_new) {
+ effect_info->InitializeResultState(effect_context.GetDspSharedResultState(i));
+ effect_info->InitializeResultState(effect_context.GetResultState(i));
+ }
+ effect_info->UpdateResultState(out_params[i].result_state,
+ effect_context.GetResultState(i));
+ }
+
+ auto consumed_input_size{effect_count *
+ static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion2))};
+ auto consumed_output_size{effect_count *
+ static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion2))};
+ if (consumed_input_size != in_header->effects_size) {
+ LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}",
+ in_header->effects_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ out_header->effects_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+ input += consumed_input_size;
+ output += consumed_output_size;
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_count,
+ EffectContext& effect_context, SplitterContext& splitter_context) {
+ s32 mix_count{0};
+ u32 consumed_input_size{0};
+
+ if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) {
+ auto in_dirty_params{reinterpret_cast<const MixInfo::InDirtyParameter*>(input)};
+ mix_count = in_dirty_params->count;
+ input += sizeof(MixInfo::InDirtyParameter);
+ consumed_input_size = static_cast<u32>(sizeof(MixInfo::InDirtyParameter) +
+ mix_count * sizeof(MixInfo::InParameter));
+ } else {
+ mix_count = mix_context.GetCount();
+ consumed_input_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter));
+ }
+
+ if (mix_buffer_count == 0) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ std::span<const MixInfo::InParameter> in_params{
+ reinterpret_cast<const MixInfo::InParameter*>(input), static_cast<size_t>(mix_count)};
+
+ u32 total_buffer_count{0};
+ for (s32 i = 0; i < mix_count; i++) {
+ const auto& params{in_params[i]};
+
+ if (params.in_use) {
+ total_buffer_count += params.buffer_count;
+ if (params.dest_mix_id > static_cast<s32>(mix_context.GetCount()) &&
+ params.dest_mix_id != UnusedMixId && params.mix_id != FinalMixId) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+ }
+ }
+
+ if (total_buffer_count > mix_buffer_count) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ bool mix_dirty{false};
+ for (s32 i = 0; i < mix_count; i++) {
+ const auto& params{in_params[i]};
+
+ s32 mix_id{i};
+ if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) {
+ mix_id = params.mix_id;
+ }
+
+ auto mix_info{mix_context.GetInfo(mix_id)};
+ if (mix_info->in_use != params.in_use) {
+ mix_info->in_use = params.in_use;
+ if (!params.in_use) {
+ mix_info->ClearEffectProcessingOrder();
+ }
+ mix_dirty = true;
+ }
+
+ if (params.in_use) {
+ mix_dirty |= mix_info->Update(mix_context.GetEdgeMatrix(), params, effect_context,
+ splitter_context, behaviour);
+ }
+ }
+
+ if (mix_dirty) {
+ if (behaviour.IsSplitterSupported() && splitter_context.UsingSplitter()) {
+ if (!mix_context.TSortInfo(splitter_context)) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+ } else {
+ mix_context.SortInfo();
+ }
+ }
+
+ if (consumed_input_size != in_header->mix_size) {
+ LOG_ERROR(Service_Audio, "Consumed an incorrect mixes size, header size={}, consumed={}",
+ in_header->mix_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += mix_count * sizeof(MixInfo::InParameter);
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools,
+ const u32 memory_pool_count) {
+ PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
+ behaviour.IsMemoryForceMappingEnabled());
+
+ std::span<const SinkInfoBase::InParameter> in_params{
+ reinterpret_cast<const SinkInfoBase::InParameter*>(input), memory_pool_count};
+ std::span<SinkInfoBase::OutStatus> out_params{
+ reinterpret_cast<SinkInfoBase::OutStatus*>(output), memory_pool_count};
+
+ const auto sink_count{sink_context.GetCount()};
+
+ for (u32 i = 0; i < sink_count; i++) {
+ const auto& params{in_params[i]};
+ auto sink_info{sink_context.GetInfo(i)};
+
+ if (sink_info->GetType() != params.type) {
+ sink_info->CleanUp();
+ switch (params.type) {
+ case SinkInfoBase::Type::Invalid:
+ std::construct_at<SinkInfoBase>(reinterpret_cast<SinkInfoBase*>(sink_info));
+ break;
+ case SinkInfoBase::Type::DeviceSink:
+ std::construct_at<DeviceSinkInfo>(reinterpret_cast<DeviceSinkInfo*>(sink_info));
+ break;
+ case SinkInfoBase::Type::CircularBufferSink:
+ std::construct_at<CircularBufferSinkInfo>(
+ reinterpret_cast<CircularBufferSinkInfo*>(sink_info));
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(params.type));
+ break;
+ }
+ }
+
+ BehaviorInfo::ErrorInfo error_info{};
+ sink_info->Update(error_info, out_params[i], params, pool_mapper);
+
+ if (error_info.error_code.IsError()) {
+ behaviour.AppendError(error_info);
+ }
+ }
+
+ const auto consumed_input_size{sink_count *
+ static_cast<u32>(sizeof(SinkInfoBase::InParameter))};
+ const auto consumed_output_size{sink_count * static_cast<u32>(sizeof(SinkInfoBase::OutStatus))};
+ if (consumed_input_size != in_header->sinks_size) {
+ LOG_ERROR(Service_Audio, "Consumed an incorrect sinks size, header size={}, consumed={}",
+ in_header->sinks_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += consumed_input_size;
+ output += consumed_output_size;
+ out_header->sinks_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools,
+ const u32 memory_pool_count) {
+ PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
+ behaviour.IsMemoryForceMappingEnabled());
+ std::span<const MemoryPoolInfo::InParameter> in_params{
+ reinterpret_cast<const MemoryPoolInfo::InParameter*>(input), memory_pool_count};
+ std::span<MemoryPoolInfo::OutStatus> out_params{
+ reinterpret_cast<MemoryPoolInfo::OutStatus*>(output), memory_pool_count};
+
+ for (size_t i = 0; i < memory_pool_count; i++) {
+ auto state{pool_mapper.Update(memory_pools[i], in_params[i], out_params[i])};
+ if (state != MemoryPoolInfo::ResultState::Success &&
+ state != MemoryPoolInfo::ResultState::BadParam &&
+ state != MemoryPoolInfo::ResultState::MapFailed &&
+ state != MemoryPoolInfo::ResultState::InUse) {
+ LOG_WARNING(Service_Audio, "Invalid ResultState from updating memory pools");
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+ }
+
+ const auto consumed_input_size{memory_pool_count *
+ static_cast<u32>(sizeof(MemoryPoolInfo::InParameter))};
+ const auto consumed_output_size{memory_pool_count *
+ static_cast<u32>(sizeof(MemoryPoolInfo::OutStatus))};
+ if (consumed_input_size != in_header->memory_pool_size) {
+ LOG_ERROR(Service_Audio,
+ "Consumed an incorrect memory pool size, header size={}, consumed={}",
+ in_header->memory_pool_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += consumed_input_size;
+ output += consumed_output_size;
+ out_header->memory_pool_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdatePerformanceBuffer(std::span<u8> performance_output,
+ const u64 performance_output_size,
+ PerformanceManager* performance_manager) {
+ auto in_params{reinterpret_cast<const PerformanceManager::InParameter*>(input)};
+ auto out_params{reinterpret_cast<PerformanceManager::OutStatus*>(output)};
+
+ if (performance_manager != nullptr) {
+ out_params->history_size =
+ performance_manager->CopyHistories(performance_output.data(), performance_output_size);
+ performance_manager->SetDetailTarget(in_params->target_node_id);
+ } else {
+ out_params->history_size = 0;
+ }
+
+ const auto consumed_input_size{static_cast<u32>(sizeof(PerformanceManager::InParameter))};
+ const auto consumed_output_size{static_cast<u32>(sizeof(PerformanceManager::OutStatus))};
+ if (consumed_input_size != in_header->performance_buffer_size) {
+ LOG_ERROR(Service_Audio,
+ "Consumed an incorrect performance size, header size={}, consumed={}",
+ in_header->performance_buffer_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += consumed_input_size;
+ output += consumed_output_size;
+ out_header->performance_buffer_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) {
+ const auto in_params{reinterpret_cast<const BehaviorInfo::InParameter*>(input)};
+
+ if (!CheckValidRevision(in_params->revision)) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ if (in_params->revision != behaviour_.GetUserRevision()) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ behaviour_.ClearError();
+ behaviour_.UpdateFlags(in_params->flags);
+
+ if (in_header->behaviour_size != sizeof(BehaviorInfo::InParameter)) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += sizeof(BehaviorInfo::InParameter);
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateErrorInfo(const BehaviorInfo& behaviour_) {
+ auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)};
+ behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count);
+
+ const auto consumed_output_size{static_cast<u32>(sizeof(BehaviorInfo::OutStatus))};
+
+ output += consumed_output_size;
+ out_header->behaviour_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
+ u32 consumed_size{0};
+ if (!splitter_context.Update(input, consumed_size)) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += consumed_size;
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateRendererInfo(const u64 elapsed_frames) {
+ struct RenderInfo {
+ /* 0x00 */ u64 frames_elapsed;
+ /* 0x08 */ char unk08[0x8];
+ };
+ static_assert(sizeof(RenderInfo) == 0x10, "RenderInfo has the wrong size!");
+
+ auto out_params{reinterpret_cast<RenderInfo*>(output)};
+ out_params->frames_elapsed = elapsed_frames;
+
+ const auto consumed_output_size{static_cast<u32>(sizeof(RenderInfo))};
+
+ output += consumed_output_size;
+ out_header->render_info_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::CheckConsumedSize() {
+ if (CpuAddr(input) - CpuAddr(input_origin.data()) != expected_input_size) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ } else if (CpuAddr(output) - CpuAddr(output_origin.data()) != expected_output_size) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+ return ResultSuccess;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/behavior/info_updater.h b/src/audio_core/renderer/behavior/info_updater.h
new file mode 100644
index 000000000..c817d8d8d
--- /dev/null
+++ b/src/audio_core/renderer/behavior/info_updater.h
@@ -0,0 +1,205 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "common/common_types.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace AudioCore::AudioRenderer {
+class BehaviorInfo;
+class VoiceContext;
+class MixContext;
+class SinkContext;
+class SplitterContext;
+class EffectContext;
+class MemoryPoolInfo;
+class PerformanceManager;
+
+class InfoUpdater {
+ struct UpdateDataHeader {
+ explicit UpdateDataHeader(u32 revision_) : revision{revision_} {}
+
+ /* 0x00 */ u32 revision;
+ /* 0x04 */ u32 behaviour_size{};
+ /* 0x08 */ u32 memory_pool_size{};
+ /* 0x0C */ u32 voices_size{};
+ /* 0x10 */ u32 voice_resources_size{};
+ /* 0x14 */ u32 effects_size{};
+ /* 0x18 */ u32 mix_size{};
+ /* 0x1C */ u32 sinks_size{};
+ /* 0x20 */ u32 performance_buffer_size{};
+ /* 0x24 */ char unk24[4];
+ /* 0x28 */ u32 render_info_size{};
+ /* 0x2C */ char unk2C[0x10];
+ /* 0x3C */ u32 size{sizeof(UpdateDataHeader)};
+ };
+ static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has the wrong size!");
+
+public:
+ explicit InfoUpdater(std::span<const u8> input, std::span<u8> output, u32 process_handle,
+ BehaviorInfo& behaviour);
+
+ /**
+ * Update the voice channel resources.
+ *
+ * @param voice_context - Voice context to update.
+ * @return Result code.
+ */
+ Result UpdateVoiceChannelResources(VoiceContext& voice_context);
+
+ /**
+ * Update voices.
+ *
+ * @param voice_context - Voice context to update.
+ * @param memory_pools - Memory pools to use for these voices.
+ * @param memory_pool_count - Number of memory pools.
+ * @return Result code.
+ */
+ Result UpdateVoices(VoiceContext& voice_context, std::span<MemoryPoolInfo> memory_pools,
+ u32 memory_pool_count);
+
+ /**
+ * Update effects.
+ *
+ * @param effect_context - Effect context to update.
+ * @param renderer_active - Whether the AudioRenderer is active.
+ * @param memory_pools - Memory pools to use for these voices.
+ * @param memory_pool_count - Number of memory pools.
+ * @return Result code.
+ */
+ Result UpdateEffects(EffectContext& effect_context, bool renderer_active,
+ std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
+
+ /**
+ * Update mixes.
+ *
+ * @param mix_context - Mix context to update.
+ * @param mix_buffer_count - Number of mix buffers.
+ * @param effect_context - Effect context to update effort order.
+ * @param splitter_context - Splitter context for the mixes.
+ * @return Result code.
+ */
+ Result UpdateMixes(MixContext& mix_context, u32 mix_buffer_count, EffectContext& effect_context,
+ SplitterContext& splitter_context);
+
+ /**
+ * Update sinks.
+ *
+ * @param sink_context - Sink context to update.
+ * @param memory_pools - Memory pools to use for these voices.
+ * @param memory_pool_count - Number of memory pools.
+ * @return Result code.
+ */
+ Result UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools,
+ u32 memory_pool_count);
+
+ /**
+ * Update memory pools.
+ *
+ * @param memory_pools - Memory pools to use for these voices.
+ * @param memory_pool_count - Number of memory pools.
+ * @return Result code.
+ */
+ Result UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
+
+ /**
+ * Update the performance buffer.
+ *
+ * @param output - Output buffer for performance metrics.
+ * @param output_size - Output buffer size.
+ * @param performance_manager - Performance manager..
+ * @return Result code.
+ */
+ Result UpdatePerformanceBuffer(std::span<u8> output, u64 output_size,
+ PerformanceManager* performance_manager);
+
+ /**
+ * Update behaviour.
+ *
+ * @param behaviour - Behaviour to update.
+ * @return Result code.
+ */
+ Result UpdateBehaviorInfo(BehaviorInfo& behaviour);
+
+ /**
+ * Update errors.
+ *
+ * @param behaviour - Behaviour to update.
+ * @return Result code.
+ */
+ Result UpdateErrorInfo(const BehaviorInfo& behaviour);
+
+ /**
+ * Update splitter.
+ *
+ * @param splitter_context - Splitter context to update.
+ * @return Result code.
+ */
+ Result UpdateSplitterInfo(SplitterContext& splitter_context);
+
+ /**
+ * Update renderer info.
+ *
+ * @param elapsed_frames - Number of elapsed frames.
+ * @return Result code.
+ */
+ Result UpdateRendererInfo(u64 elapsed_frames);
+
+ /**
+ * Check that the input.output sizes match their expected values.
+ *
+ * @return Result code.
+ */
+ Result CheckConsumedSize();
+
+private:
+ /**
+ * Update effects version 1.
+ *
+ * @param effect_context - Effect context to update.
+ * @param renderer_active - Is the AudioRenderer active?
+ * @param memory_pools - Memory pools to use for these voices.
+ * @param memory_pool_count - Number of memory pools.
+ * @return Result code.
+ */
+ Result UpdateEffectsVersion1(EffectContext& effect_context, bool renderer_active,
+ std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
+
+ /**
+ * Update effects version 2.
+ *
+ * @param effect_context - Effect context to update.
+ * @param renderer_active - Is the AudioRenderer active?
+ * @param memory_pools - Memory pools to use for these voices.
+ * @param memory_pool_count - Number of memory pools.
+ * @return Result code.
+ */
+ Result UpdateEffectsVersion2(EffectContext& effect_context, bool renderer_active,
+ std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
+
+ /// Input buffer
+ u8 const* input;
+ /// Input buffer start
+ std::span<const u8> input_origin;
+ /// Output buffer start
+ u8* output;
+ /// Output buffer start
+ std::span<u8> output_origin;
+ /// Input header
+ const UpdateDataHeader* in_header;
+ /// Output header
+ UpdateDataHeader* out_header;
+ /// Expected input size, see CheckConsumedSize
+ u64 expected_input_size;
+ /// Expected output size, see CheckConsumedSize
+ u64 expected_output_size;
+ /// Unused
+ u32 process_handle;
+ /// Behaviour
+ BehaviorInfo& behaviour;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp
new file mode 100644
index 000000000..2ef879ee1
--- /dev/null
+++ b/src/audio_core/renderer/command/command_buffer.cpp
@@ -0,0 +1,714 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/command/command_buffer.h"
+#include "audio_core/renderer/command/command_list_header.h"
+#include "audio_core/renderer/command/command_processing_time_estimator.h"
+#include "audio_core/renderer/effect/biquad_filter.h"
+#include "audio_core/renderer/effect/delay.h"
+#include "audio_core/renderer/effect/reverb.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "audio_core/renderer/mix/mix_info.h"
+#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
+#include "audio_core/renderer/sink/device_sink_info.h"
+#include "audio_core/renderer/sink/sink_info_base.h"
+#include "audio_core/renderer/voice/voice_info.h"
+#include "audio_core/renderer/voice/voice_state.h"
+
+namespace AudioCore::AudioRenderer {
+
+template <typename T, CommandId Id>
+T& CommandBuffer::GenerateStart(const s32 node_id) {
+ if (size + sizeof(T) >= command_list.size_bytes()) {
+ LOG_ERROR(
+ Service_Audio,
+ "Attempting to write commands beyond the end of allocated command buffer memory!");
+ UNREACHABLE();
+ }
+
+ auto& cmd{*std::construct_at<T>(reinterpret_cast<T*>(&command_list[size]))};
+
+ cmd.magic = CommandMagic;
+ cmd.enabled = true;
+ cmd.type = Id;
+ cmd.size = sizeof(T);
+ cmd.node_id = node_id;
+
+ return cmd;
+}
+
+template <typename T>
+void CommandBuffer::GenerateEnd(T& cmd) {
+ cmd.estimated_process_time = time_estimator->Estimate(cmd);
+ estimated_process_time += cmd.estimated_process_time;
+ size += sizeof(T);
+ count++;
+}
+
+void CommandBuffer::GeneratePcmInt16Version1Command(const s32 node_id,
+ const MemoryPoolInfo& memory_pool_,
+ VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{
+ GenerateStart<PcmInt16DataSourceVersion1Command, CommandId::DataSourcePcmInt16Version1>(
+ node_id)};
+
+ cmd.src_quality = voice_info.src_quality;
+ cmd.output_index = buffer_count + channel;
+ cmd.flags = voice_info.flags & 3;
+ cmd.sample_rate = voice_info.sample_rate;
+ cmd.pitch = voice_info.pitch;
+ cmd.channel_index = channel;
+ cmd.channel_count = voice_info.channel_count;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
+ }
+
+ cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
+
+ GenerateEnd<PcmInt16DataSourceVersion1Command>(cmd);
+}
+
+void CommandBuffer::GeneratePcmInt16Version2Command(const s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{
+ GenerateStart<PcmInt16DataSourceVersion2Command, CommandId::DataSourcePcmInt16Version2>(
+ node_id)};
+
+ cmd.src_quality = voice_info.src_quality;
+ cmd.output_index = buffer_count + channel;
+ cmd.flags = voice_info.flags & 3;
+ cmd.sample_rate = voice_info.sample_rate;
+ cmd.pitch = voice_info.pitch;
+ cmd.channel_index = channel;
+ cmd.channel_count = voice_info.channel_count;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
+ }
+
+ cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
+
+ GenerateEnd<PcmInt16DataSourceVersion2Command>(cmd);
+}
+
+void CommandBuffer::GeneratePcmFloatVersion1Command(const s32 node_id,
+ const MemoryPoolInfo& memory_pool_,
+ VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{
+ GenerateStart<PcmFloatDataSourceVersion1Command, CommandId::DataSourcePcmFloatVersion1>(
+ node_id)};
+
+ cmd.src_quality = voice_info.src_quality;
+ cmd.output_index = buffer_count + channel;
+ cmd.flags = voice_info.flags & 3;
+ cmd.sample_rate = voice_info.sample_rate;
+ cmd.pitch = voice_info.pitch;
+ cmd.channel_index = channel;
+ cmd.channel_count = voice_info.channel_count;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
+ }
+
+ cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
+
+ GenerateEnd<PcmFloatDataSourceVersion1Command>(cmd);
+}
+
+void CommandBuffer::GeneratePcmFloatVersion2Command(const s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{
+ GenerateStart<PcmFloatDataSourceVersion2Command, CommandId::DataSourcePcmFloatVersion2>(
+ node_id)};
+
+ cmd.src_quality = voice_info.src_quality;
+ cmd.output_index = buffer_count + channel;
+ cmd.flags = voice_info.flags & 3;
+ cmd.sample_rate = voice_info.sample_rate;
+ cmd.pitch = voice_info.pitch;
+ cmd.channel_index = channel;
+ cmd.channel_count = voice_info.channel_count;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
+ }
+
+ cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
+
+ GenerateEnd<PcmFloatDataSourceVersion2Command>(cmd);
+}
+
+void CommandBuffer::GenerateAdpcmVersion1Command(const s32 node_id,
+ const MemoryPoolInfo& memory_pool_,
+ VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{
+ GenerateStart<AdpcmDataSourceVersion1Command, CommandId::DataSourceAdpcmVersion1>(node_id)};
+
+ cmd.src_quality = voice_info.src_quality;
+ cmd.output_index = buffer_count + channel;
+ cmd.flags = voice_info.flags & 3;
+ cmd.sample_rate = voice_info.sample_rate;
+ cmd.pitch = voice_info.pitch;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
+ }
+
+ cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
+ cmd.data_address = voice_info.data_address.GetReference(true);
+ cmd.data_size = voice_info.data_address.GetSize();
+
+ GenerateEnd<AdpcmDataSourceVersion1Command>(cmd);
+}
+
+void CommandBuffer::GenerateAdpcmVersion2Command(const s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{
+ GenerateStart<AdpcmDataSourceVersion2Command, CommandId::DataSourceAdpcmVersion2>(node_id)};
+
+ cmd.src_quality = voice_info.src_quality;
+ cmd.output_index = buffer_count + channel;
+ cmd.flags = voice_info.flags & 3;
+ cmd.sample_rate = voice_info.sample_rate;
+ cmd.pitch = voice_info.pitch;
+ cmd.channel_index = channel;
+ cmd.channel_count = voice_info.channel_count;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
+ }
+
+ cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
+ cmd.data_address = voice_info.data_address.GetReference(true);
+ cmd.data_size = voice_info.data_address.GetSize();
+
+ GenerateEnd<AdpcmDataSourceVersion2Command>(cmd);
+}
+
+void CommandBuffer::GenerateVolumeCommand(const s32 node_id, const s16 buffer_offset,
+ const s16 input_index, const f32 volume,
+ const u8 precision) {
+ auto& cmd{GenerateStart<VolumeCommand, CommandId::Volume>(node_id)};
+
+ cmd.precision = precision;
+ cmd.input_index = buffer_offset + input_index;
+ cmd.output_index = buffer_offset + input_index;
+ cmd.volume = volume;
+
+ GenerateEnd<VolumeCommand>(cmd);
+}
+
+void CommandBuffer::GenerateVolumeRampCommand(const s32 node_id, VoiceInfo& voice_info,
+ const s16 buffer_count, const u8 precision) {
+ auto& cmd{GenerateStart<VolumeRampCommand, CommandId::VolumeRamp>(node_id)};
+
+ cmd.input_index = buffer_count;
+ cmd.output_index = buffer_count;
+ cmd.prev_volume = voice_info.prev_volume;
+ cmd.volume = voice_info.volume;
+ cmd.precision = precision;
+
+ GenerateEnd<VolumeRampCommand>(cmd);
+}
+
+void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel,
+ const u32 biquad_index,
+ const bool use_float_processing) {
+ auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
+
+ cmd.input = buffer_count + channel;
+ cmd.output = buffer_count + channel;
+
+ cmd.biquad = voice_info.biquads[biquad_index];
+
+ cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()),
+ MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
+
+ cmd.needs_init = !voice_info.biquad_initialized[biquad_index];
+ cmd.use_float_processing = use_float_processing;
+
+ GenerateEnd<BiquadFilterCommand>(cmd);
+}
+
+void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 buffer_offset, const s8 channel,
+ const bool needs_init,
+ const bool use_float_processing) {
+ auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
+
+ const auto& parameter{
+ *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ const auto state{
+ reinterpret_cast<VoiceState::BiquadFilterState*>(effect_info.GetStateBuffer())};
+
+ cmd.input = buffer_offset + parameter.inputs[channel];
+ cmd.output = buffer_offset + parameter.outputs[channel];
+
+ cmd.biquad.b = parameter.b;
+ cmd.biquad.a = parameter.a;
+
+ cmd.state = memory_pool->Translate(CpuAddr(state),
+ MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
+
+ cmd.needs_init = needs_init;
+ cmd.use_float_processing = use_float_processing;
+
+ GenerateEnd<BiquadFilterCommand>(cmd);
+}
+
+void CommandBuffer::GenerateMixCommand(const s32 node_id, const s16 input_index,
+ const s16 output_index, const s16 buffer_offset,
+ const f32 volume, const u8 precision) {
+ auto& cmd{GenerateStart<MixCommand, CommandId::Mix>(node_id)};
+
+ cmd.input_index = input_index;
+ cmd.output_index = output_index;
+ cmd.volume = volume;
+ cmd.precision = precision;
+
+ GenerateEnd<MixCommand>(cmd);
+}
+
+void CommandBuffer::GenerateMixRampCommand(const s32 node_id,
+ [[maybe_unused]] const s16 buffer_count,
+ const s16 input_index, const s16 output_index,
+ const f32 volume, const f32 prev_volume,
+ const CpuAddr prev_samples, const u8 precision) {
+ if (volume == 0.0f && prev_volume == 0.0f) {
+ return;
+ }
+
+ auto& cmd{GenerateStart<MixRampCommand, CommandId::MixRamp>(node_id)};
+
+ cmd.input_index = input_index;
+ cmd.output_index = output_index;
+ cmd.prev_volume = prev_volume;
+ cmd.volume = volume;
+ cmd.previous_sample = prev_samples;
+ cmd.precision = precision;
+
+ GenerateEnd<MixRampCommand>(cmd);
+}
+
+void CommandBuffer::GenerateMixRampGroupedCommand(const s32 node_id, const s16 buffer_count,
+ const s16 input_index, s16 output_index,
+ std::span<const f32> volumes,
+ std::span<const f32> prev_volumes,
+ const CpuAddr prev_samples, const u8 precision) {
+ auto& cmd{GenerateStart<MixRampGroupedCommand, CommandId::MixRampGrouped>(node_id)};
+
+ cmd.buffer_count = buffer_count;
+
+ for (s32 i = 0; i < buffer_count; i++) {
+ cmd.inputs[i] = input_index;
+ cmd.outputs[i] = output_index++;
+ cmd.prev_volumes[i] = prev_volumes[i];
+ cmd.volumes[i] = volumes[i];
+ }
+
+ cmd.previous_samples = prev_samples;
+ cmd.precision = precision;
+
+ GenerateEnd<MixRampGroupedCommand>(cmd);
+}
+
+void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceState& voice_state,
+ std::span<const s32> buffer, const s16 buffer_count,
+ s16 buffer_offset, const bool was_playing) {
+ auto& cmd{GenerateStart<DepopPrepareCommand, CommandId::DepopPrepare>(node_id)};
+
+ cmd.enabled = was_playing;
+
+ for (u32 i = 0; i < MaxMixBuffers; i++) {
+ cmd.inputs[i] = buffer_offset++;
+ }
+
+ cmd.previous_samples = memory_pool->Translate(CpuAddr(voice_state.previous_samples.data()),
+ MaxMixBuffers * sizeof(s32));
+ cmd.buffer_count = buffer_count;
+ cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer.size_bytes());
+
+ GenerateEnd<DepopPrepareCommand>(cmd);
+}
+
+void CommandBuffer::GenerateDepopForMixBuffersCommand(const s32 node_id, const MixInfo& mix_info,
+ std::span<const s32> depop_buffer) {
+ auto& cmd{GenerateStart<DepopForMixBuffersCommand, CommandId::DepopForMixBuffers>(node_id)};
+
+ cmd.input = mix_info.buffer_offset;
+ cmd.count = mix_info.buffer_count;
+ cmd.decay = mix_info.sample_rate == TargetSampleRate ? 0.96218872f : 0.94369507f;
+ cmd.depop_buffer =
+ memory_pool->Translate(CpuAddr(depop_buffer.data()), mix_info.buffer_count * sizeof(s32));
+
+ GenerateEnd<DepopForMixBuffersCommand>(cmd);
+}
+
+void CommandBuffer::GenerateDelayCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 buffer_offset) {
+ auto& cmd{GenerateStart<DelayCommand, CommandId::Delay>(node_id)};
+
+ const auto& parameter{
+ *reinterpret_cast<DelayInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ const auto state{effect_info.GetStateBuffer()};
+
+ if (IsChannelCountValid(parameter.channel_count)) {
+ const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(DelayInfo::State))};
+ if (state_buffer) {
+ for (s16 channel = 0; channel < parameter.channel_count; channel++) {
+ cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
+ cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
+ }
+
+ if (!behavior->IsDelayChannelMappingChanged() && parameter.channel_count == 6) {
+ UseOldChannelMapping(cmd.inputs, cmd.outputs);
+ }
+
+ cmd.parameter = parameter;
+ cmd.effect_enabled = effect_info.IsEnabled();
+ cmd.state = state_buffer;
+ cmd.workbuffer = effect_info.GetWorkbuffer(-1);
+ }
+ }
+
+ GenerateEnd<DelayCommand>(cmd);
+}
+
+void CommandBuffer::GenerateUpsampleCommand(const s32 node_id, const s16 buffer_offset,
+ UpsamplerInfo& upsampler_info, const u32 input_count,
+ std::span<const s8> inputs, const s16 buffer_count,
+ const u32 sample_count_, const u32 sample_rate_) {
+ auto& cmd{GenerateStart<UpsampleCommand, CommandId::Upsample>(node_id)};
+
+ cmd.samples_buffer = memory_pool->Translate(upsampler_info.samples_pos,
+ upsampler_info.sample_count * sizeof(s32));
+ cmd.inputs = memory_pool->Translate(CpuAddr(upsampler_info.inputs.data()), MaxChannels);
+ cmd.buffer_count = buffer_count;
+ cmd.unk_20 = 0;
+ cmd.source_sample_count = sample_count_;
+ cmd.source_sample_rate = sample_rate_;
+
+ upsampler_info.input_count = input_count;
+ for (u32 i = 0; i < input_count; i++) {
+ upsampler_info.inputs[i] = buffer_offset + inputs[i];
+ }
+
+ cmd.upsampler_info = memory_pool->Translate(CpuAddr(&upsampler_info), sizeof(UpsamplerInfo));
+
+ GenerateEnd<UpsampleCommand>(cmd);
+}
+
+void CommandBuffer::GenerateDownMix6chTo2chCommand(const s32 node_id, std::span<const s8> inputs,
+ const s16 buffer_offset,
+ std::span<const f32> downmix_coeff) {
+ auto& cmd{GenerateStart<DownMix6chTo2chCommand, CommandId::DownMix6chTo2ch>(node_id)};
+
+ for (u32 i = 0; i < MaxChannels; i++) {
+ cmd.inputs[i] = buffer_offset + inputs[i];
+ cmd.outputs[i] = buffer_offset + inputs[i];
+ }
+
+ for (u32 i = 0; i < 4; i++) {
+ cmd.down_mix_coeff[i] = downmix_coeff[i];
+ }
+
+ GenerateEnd<DownMix6chTo2chCommand>(cmd);
+}
+
+void CommandBuffer::GenerateAuxCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 input_index, const s16 output_index,
+ const s16 buffer_offset, const u32 update_count,
+ const u32 count_max, const u32 write_offset) {
+ auto& cmd{GenerateStart<AuxCommand, CommandId::Aux>(node_id)};
+
+ if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) {
+ cmd.input = buffer_offset + input_index;
+ cmd.output = buffer_offset + output_index;
+ cmd.send_buffer_info = effect_info.GetSendBufferInfo();
+ cmd.send_buffer = effect_info.GetSendBuffer();
+ cmd.return_buffer_info = effect_info.GetReturnBufferInfo();
+ cmd.return_buffer = effect_info.GetReturnBuffer();
+ cmd.count_max = count_max;
+ cmd.write_offset = write_offset;
+ cmd.update_count = update_count;
+ cmd.effect_enabled = effect_info.IsEnabled();
+ }
+
+ GenerateEnd<AuxCommand>(cmd);
+}
+
+void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffer_offset,
+ SinkInfoBase& sink_info, const u32 session_id,
+ std::span<s32> samples_buffer) {
+ auto& cmd{GenerateStart<DeviceSinkCommand, CommandId::DeviceSink>(node_id)};
+ const auto& parameter{
+ *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())};
+ auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())};
+
+ cmd.session_id = session_id;
+
+ if (state.upsampler_info != nullptr) {
+ const auto size_{state.upsampler_info->sample_count * parameter.input_count};
+ const auto size_bytes{size_ * sizeof(s32)};
+ const auto addr{memory_pool->Translate(state.upsampler_info->samples_pos, size_bytes)};
+ cmd.sample_buffer = {reinterpret_cast<s32*>(addr),
+ parameter.input_count * state.upsampler_info->sample_count};
+ } else {
+ cmd.sample_buffer = samples_buffer;
+ }
+
+ cmd.input_count = parameter.input_count;
+ for (u32 i = 0; i < parameter.input_count; i++) {
+ cmd.inputs[i] = buffer_offset + parameter.inputs[i];
+ }
+
+ GenerateEnd<DeviceSinkCommand>(cmd);
+}
+
+void CommandBuffer::GenerateCircularBufferSinkCommand(const s32 node_id, SinkInfoBase& sink_info,
+ const s16 buffer_offset) {
+ auto& cmd{GenerateStart<CircularBufferSinkCommand, CommandId::CircularBufferSink>(node_id)};
+ const auto& parameter{*reinterpret_cast<CircularBufferSinkInfo::CircularBufferInParameter*>(
+ sink_info.GetParameter())};
+ auto state{
+ *reinterpret_cast<CircularBufferSinkInfo::CircularBufferState*>(sink_info.GetState())};
+
+ cmd.input_count = parameter.input_count;
+ for (u32 i = 0; i < parameter.input_count; i++) {
+ cmd.inputs[i] = buffer_offset + parameter.inputs[i];
+ }
+
+ cmd.address = state.address_info.GetReference(true);
+ cmd.size = parameter.size;
+ cmd.pos = state.current_pos;
+
+ GenerateEnd<CircularBufferSinkCommand>(cmd);
+}
+
+void CommandBuffer::GenerateReverbCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 buffer_offset,
+ const bool long_size_pre_delay_supported) {
+ auto& cmd{GenerateStart<ReverbCommand, CommandId::Reverb>(node_id)};
+
+ const auto& parameter{
+ *reinterpret_cast<ReverbInfo::ParameterVersion2*>(effect_info.GetParameter())};
+ const auto state{effect_info.GetStateBuffer()};
+
+ if (IsChannelCountValid(parameter.channel_count)) {
+ const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(ReverbInfo::State))};
+ if (state_buffer) {
+ for (s16 channel = 0; channel < parameter.channel_count; channel++) {
+ cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
+ cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
+ }
+
+ if (!behavior->IsReverbChannelMappingChanged() && parameter.channel_count == 6) {
+ UseOldChannelMapping(cmd.inputs, cmd.outputs);
+ }
+
+ cmd.parameter = parameter;
+ cmd.effect_enabled = effect_info.IsEnabled();
+ cmd.state = state_buffer;
+ cmd.workbuffer = effect_info.GetWorkbuffer(-1);
+ cmd.long_size_pre_delay_supported = long_size_pre_delay_supported;
+ }
+ }
+
+ GenerateEnd<ReverbCommand>(cmd);
+}
+
+void CommandBuffer::GenerateI3dl2ReverbCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 buffer_offset) {
+ auto& cmd{GenerateStart<I3dl2ReverbCommand, CommandId::I3dl2Reverb>(node_id)};
+
+ const auto& parameter{
+ *reinterpret_cast<I3dl2ReverbInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ const auto state{effect_info.GetStateBuffer()};
+
+ if (IsChannelCountValid(parameter.channel_count)) {
+ const auto state_buffer{
+ memory_pool->Translate(CpuAddr(state), sizeof(I3dl2ReverbInfo::State))};
+ if (state_buffer) {
+ for (s16 channel = 0; channel < parameter.channel_count; channel++) {
+ cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
+ cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
+ }
+
+ if (!behavior->IsI3dl2ReverbChannelMappingChanged() && parameter.channel_count == 6) {
+ UseOldChannelMapping(cmd.inputs, cmd.outputs);
+ }
+
+ cmd.parameter = parameter;
+ cmd.effect_enabled = effect_info.IsEnabled();
+ cmd.state = state_buffer;
+ cmd.workbuffer = effect_info.GetWorkbuffer(-1);
+ }
+ }
+
+ GenerateEnd<I3dl2ReverbCommand>(cmd);
+}
+
+void CommandBuffer::GeneratePerformanceCommand(const s32 node_id, const PerformanceState state,
+ const PerformanceEntryAddresses& entry_addresses) {
+ auto& cmd{GenerateStart<PerformanceCommand, CommandId::Performance>(node_id)};
+
+ cmd.state = state;
+ cmd.entry_address = entry_addresses;
+
+ GenerateEnd<PerformanceCommand>(cmd);
+}
+
+void CommandBuffer::GenerateClearMixCommand(const s32 node_id) {
+ auto& cmd{GenerateStart<ClearMixBufferCommand, CommandId::ClearMixBuffer>(node_id)};
+ GenerateEnd<ClearMixBufferCommand>(cmd);
+}
+
+void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 buffer_offset, const s8 channel) {
+ auto& cmd{GenerateStart<CopyMixBufferCommand, CommandId::CopyMixBuffer>(node_id)};
+
+ const auto& parameter{
+ *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ cmd.input_index = buffer_offset + parameter.inputs[channel];
+ cmd.output_index = buffer_offset + parameter.outputs[channel];
+
+ GenerateEnd<CopyMixBufferCommand>(cmd);
+}
+
+void CommandBuffer::GenerateLightLimiterCommand(
+ const s32 node_id, const s16 buffer_offset,
+ const LightLimiterInfo::ParameterVersion1& parameter, const LightLimiterInfo::State& state,
+ const bool enabled, const CpuAddr workbuffer) {
+ auto& cmd{GenerateStart<LightLimiterVersion1Command, CommandId::LightLimiterVersion1>(node_id)};
+
+ if (IsChannelCountValid(parameter.channel_count)) {
+ const auto state_buffer{
+ memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))};
+ if (state_buffer) {
+ for (s8 channel = 0; channel < parameter.channel_count; channel++) {
+ cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
+ cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
+ }
+
+ std::memcpy(&cmd.parameter, &parameter, sizeof(LightLimiterInfo::ParameterVersion1));
+ cmd.effect_enabled = enabled;
+ cmd.state = state_buffer;
+ cmd.workbuffer = workbuffer;
+ }
+ }
+
+ GenerateEnd<LightLimiterVersion1Command>(cmd);
+}
+
+void CommandBuffer::GenerateLightLimiterCommand(
+ const s32 node_id, const s16 buffer_offset,
+ const LightLimiterInfo::ParameterVersion2& parameter,
+ const LightLimiterInfo::StatisticsInternal& statistics, const LightLimiterInfo::State& state,
+ const bool enabled, const CpuAddr workbuffer) {
+ auto& cmd{GenerateStart<LightLimiterVersion2Command, CommandId::LightLimiterVersion2>(node_id)};
+ if (IsChannelCountValid(parameter.channel_count)) {
+ const auto state_buffer{
+ memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))};
+ if (state_buffer) {
+ for (s8 channel = 0; channel < parameter.channel_count; channel++) {
+ cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
+ cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
+ }
+
+ cmd.parameter = parameter;
+ cmd.effect_enabled = enabled;
+ cmd.state = state_buffer;
+ if (cmd.parameter.statistics_enabled) {
+ cmd.result_state = memory_pool->Translate(
+ CpuAddr(&statistics), sizeof(LightLimiterInfo::StatisticsInternal));
+ } else {
+ cmd.result_state = 0;
+ }
+ cmd.workbuffer = workbuffer;
+ }
+ }
+
+ GenerateEnd<LightLimiterVersion2Command>(cmd);
+}
+
+void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{GenerateStart<MultiTapBiquadFilterCommand, CommandId::MultiTapBiquadFilter>(node_id)};
+
+ cmd.input = buffer_count + channel;
+ cmd.output = buffer_count + channel;
+ cmd.biquads = voice_info.biquads;
+
+ cmd.states[0] =
+ memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()),
+ MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
+ cmd.states[1] =
+ memory_pool->Translate(CpuAddr(voice_state.biquad_states[1].data()),
+ MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
+
+ cmd.needs_init[0] = !voice_info.biquad_initialized[0];
+ cmd.needs_init[1] = !voice_info.biquad_initialized[1];
+ cmd.filter_tap_count = MaxBiquadFilters;
+
+ GenerateEnd<MultiTapBiquadFilterCommand>(cmd);
+}
+
+void CommandBuffer::GenerateCaptureCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 input_index, const s16 output_index,
+ const s16 buffer_offset, const u32 update_count,
+ const u32 count_max, const u32 write_offset) {
+ auto& cmd{GenerateStart<CaptureCommand, CommandId::Capture>(node_id)};
+
+ if (effect_info.GetSendBuffer()) {
+ cmd.input = buffer_offset + input_index;
+ cmd.output = buffer_offset + output_index;
+ cmd.send_buffer_info = effect_info.GetSendBufferInfo();
+ cmd.send_buffer = effect_info.GetSendBuffer();
+ cmd.count_max = count_max;
+ cmd.write_offset = write_offset;
+ cmd.update_count = update_count;
+ cmd.effect_enabled = effect_info.IsEnabled();
+ }
+
+ GenerateEnd<CaptureCommand>(cmd);
+}
+
+void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info,
+ s32 node_id) {
+ auto& cmd{GenerateStart<CompressorCommand, CommandId::Compressor>(node_id)};
+
+ auto& parameter{
+ *reinterpret_cast<CompressorInfo::ParameterVersion2*>(effect_info.GetParameter())};
+ auto state{reinterpret_cast<CompressorInfo::State*>(effect_info.GetStateBuffer())};
+
+ if (IsChannelCountValid(parameter.channel_count)) {
+ auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(CompressorInfo::State))};
+ if (state_buffer) {
+ for (u16 channel = 0; channel < parameter.channel_count; channel++) {
+ cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
+ cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
+ }
+ cmd.parameter = parameter;
+ cmd.workbuffer = state_buffer;
+ cmd.enabled = effect_info.IsEnabled();
+ }
+ }
+
+ GenerateEnd<CompressorCommand>(cmd);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_buffer.h b/src/audio_core/renderer/command/command_buffer.h
new file mode 100644
index 000000000..162170846
--- /dev/null
+++ b/src/audio_core/renderer/command/command_buffer.h
@@ -0,0 +1,468 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/command/commands.h"
+#include "audio_core/renderer/effect/light_limiter.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+struct UpsamplerInfo;
+struct VoiceState;
+class EffectInfoBase;
+class ICommandProcessingTimeEstimator;
+class MixInfo;
+class MemoryPoolInfo;
+class SinkInfoBase;
+class VoiceInfo;
+
+/**
+ * Utility functions to generate and add commands into the current command list.
+ */
+class CommandBuffer {
+public:
+ /**
+ * Generate a PCM s16 version 1 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param memory_pool - Memory pool for translating buffer addresses to the DSP.
+ * @param voice_info - The voice info this command is generated from.
+ * @param voice_state - The voice state the DSP will use for this command.
+ * @param buffer_count - Number of mix buffers in use,
+ * data will be read into this index + channel.
+ * @param channel - Channel index for this command.
+ */
+ void GeneratePcmInt16Version1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
+ VoiceInfo& voice_info, const VoiceState& voice_state,
+ s16 buffer_count, s8 channel);
+
+ /**
+ * Generate a PCM s16 version 2 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_info - The voice info this command is generated from.
+ * @param voice_state - The voice state the DSP will use for this command.
+ * @param buffer_count - Number of mix buffers in use,
+ * data will be read into this index + channel.
+ * @param channel - Channel index for this command.
+ */
+ void GeneratePcmInt16Version2Command(s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state, s16 buffer_count,
+ s8 channel);
+
+ /**
+ * Generate a PCM f32 version 1 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param memory_pool - Memory pool for translating buffer addresses to the DSP.
+ * @param voice_info - The voice info this command is generated from.
+ * @param voice_state - The voice state the DSP will use for this command.
+ * @param buffer_count - Number of mix buffers in use,
+ * data will be read into this index + channel.
+ * @param channel - Channel index for this command.
+ */
+ void GeneratePcmFloatVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
+ VoiceInfo& voice_info, const VoiceState& voice_state,
+ s16 buffer_count, s8 channel);
+
+ /**
+ * Generate a PCM f32 version 2 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_info - The voice info this command is generated from.
+ * @param voice_state - The voice state the DSP will use for this command.
+ * @param buffer_count - Number of mix buffers in use,
+ * data will be read into this index + channel.
+ * @param channel - Channel index for this command.
+ */
+ void GeneratePcmFloatVersion2Command(s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state, s16 buffer_count,
+ s8 channel);
+
+ /**
+ * Generate an ADPCM version 1 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param memory_pool - Memory pool for translating buffer addresses to the DSP.
+ * @param voice_info - The voice info this command is generated from.
+ * @param voice_state - The voice state the DSP will use for this command.
+ * @param buffer_count - Number of mix buffers in use,
+ * data will be read into this index + channel.
+ * @param channel - Channel index for this command.
+ */
+ void GenerateAdpcmVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
+ VoiceInfo& voice_info, const VoiceState& voice_state,
+ s16 buffer_count, s8 channel);
+
+ /**
+ * Generate an ADPCM version 2 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_info - The voice info this command is generated from.
+ * @param voice_state - The voice state the DSP will use for this command.
+ * @param buffer_count - Number of mix buffers in use,
+ * data will be read into this index + channel.
+ * @param channel - Channel index for this command.
+ */
+ void GenerateAdpcmVersion2Command(s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state, s16 buffer_count, s8 channel);
+
+ /**
+ * Generate a volume command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_offset - Base mix buffer index to generate this command at.
+ * @param input_index - Channel index and mix buffer offset for this command.
+ * @param volume - Mix volume added to the input samples.
+ * @param precision - Number of decimal bits for fixed point operations.
+ */
+ void GenerateVolumeCommand(s32 node_id, s16 buffer_offset, s16 input_index, f32 volume,
+ u8 precision);
+
+ /**
+ * Generate a volume ramp command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_info - The voice info this command takes its volumes from.
+ * @param buffer_count - Number of active mix buffers, command will generate at this index.
+ * @param precision - Number of decimal bits for fixed point operations.
+ */
+ void GenerateVolumeRampCommand(s32 node_id, VoiceInfo& voice_info, s16 buffer_count,
+ u8 precision);
+
+ /**
+ * Generate a biquad filter command from a voice, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_info - The voice info this command takes biquad parameters from.
+ * @param voice_state - Used by the AudioRenderer to track previous samples.
+ * @param buffer_count - Number of active mix buffers,
+ * command will generate at this index + channel.
+ * @param channel - Channel index for this filter to work on.
+ * @param biquad_index - Which biquad filter to use for this command (0-1).
+ * @param use_float_processing - Should int or float processing be used?
+ */
+ void GenerateBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state, s16 buffer_count, s8 channel,
+ u32 biquad_index, bool use_float_processing);
+
+ /**
+ * Generate a biquad filter effect command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - The effect info this command takes biquad parameters from.
+ * @param buffer_offset - Mix buffer offset this command will use,
+ * command will generate at this index + channel.
+ * @param channel - Channel index for this filter to work on.
+ * @param needs_init - True if the biquad state needs initialisation.
+ * @param use_float_processing - Should int or float processing be used?
+ */
+ void GenerateBiquadFilterCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
+ s8 channel, bool needs_init, bool use_float_processing);
+
+ /**
+ * Generate a mix command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param input_index - Input mix buffer index for this command.
+ * Added to the buffer offset.
+ * @param output_index - Output mix buffer index for this command.
+ * Added to the buffer offset.
+ * @param buffer_offset - Mix buffer offset this command will use.
+ * @param volume - Volume to be applied to the input.
+ * @param precision - Number of decimal bits for fixed point operations.
+ */
+ void GenerateMixCommand(s32 node_id, s16 input_index, s16 output_index, s16 buffer_offset,
+ f32 volume, u8 precision);
+
+ /**
+ * Generate a mix ramp command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_count - Number of active mix buffers.
+ * @param input_index - Input mix buffer index for this command.
+ * Added to buffer_count.
+ * @param output_index - Output mix buffer index for this command.
+ * Added to buffer_count.
+ * @param volume - Current mix volume used for calculating the ramp.
+ * @param prev_volume - Previous mix volume, used for calculating the ramp,
+ * also applied to the input.
+ * @param prev_samples - Previous sample buffer. Used for depopping.
+ * @param precision - Number of decimal bits for fixed point operations.
+ */
+ void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
+ f32 volume, f32 prev_volume, CpuAddr prev_samples, u8 precision);
+
+ /**
+ * Generate a mix ramp grouped command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_count - Number of active mix buffers.
+ * @param input_index - Input mix buffer index for this command.
+ * Added to buffer_count.
+ * @param output_index - Output mix buffer index for this command.
+ * Added to buffer_count.
+ * @param volumes - Current mix volumes used for calculating the ramp.
+ * @param prev_volumes - Previous mix volumes, used for calculating the ramp,
+ * also applied to the input.
+ * @param prev_samples - Previous sample buffer. Used for depopping.
+ * @param precision - Number of decimal bits for fixed point operations.
+ */
+ void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
+ s16 output_index, std::span<const f32> volumes,
+ std::span<const f32> prev_volumes, CpuAddr prev_samples,
+ u8 precision);
+
+ /**
+ * Generate a depop prepare command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_state - State to track the previous depop samples for each mix buffer.
+ * @param buffer - State to track the current depop samples for each mix buffer.
+ * @param buffer_count - Number of active mix buffers.
+ * @param buffer_offset - Base mix buffer index to generate the channel depops at.
+ * @param was_playing - Command only needs to work if the voice was previously playing.
+ */
+ void GenerateDepopPrepareCommand(s32 node_id, const VoiceState& voice_state,
+ std::span<const s32> buffer, s16 buffer_count,
+ s16 buffer_offset, bool was_playing);
+
+ /**
+ * Generate a depop command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param mix_info - Mix info to get the buffer count and base offsets from.
+ * @param depop_buffer - Buffer of current depop sample values to be added to the input
+ * channels.
+ */
+ void GenerateDepopForMixBuffersCommand(s32 node_id, const MixInfo& mix_info,
+ std::span<const s32> depop_buffer);
+
+ /**
+ * Generate a delay command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - Delay effect info to generate this command from.
+ * @param buffer_offset - Base mix buffer offset to apply the apply the delay.
+ */
+ void GenerateDelayCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
+
+ /**
+ * Generate an upsample command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_offset - Base mix buffer offset to upsample.
+ * @param upsampler_info - Upsampler info to control the upsampling.
+ * @param input_count - Number of input channels to upsample.
+ * @param inputs - Input mix buffer indexes.
+ * @param buffer_count - Number of active mix buffers.
+ * @param sample_count - Source sample count of the input.
+ * @param sample_rate - Source sample rate of the input.
+ */
+ void GenerateUpsampleCommand(s32 node_id, s16 buffer_offset, UpsamplerInfo& upsampler_info,
+ u32 input_count, std::span<const s8> inputs, s16 buffer_count,
+ u32 sample_count, u32 sample_rate);
+
+ /**
+ * Generate a downmix 6 -> 2 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param inputs - Input mix buffer indexes.
+ * @param buffer_offset - Base mix buffer offset of the channels to downmix.
+ * @param downmix_coeff - Downmixing coefficients.
+ */
+ void GenerateDownMix6chTo2chCommand(s32 node_id, std::span<const s8> inputs, s16 buffer_offset,
+ std::span<const f32> downmix_coeff);
+
+ /**
+ * Generate an aux buffer command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - Aux effect info to generate this command from.
+ * @param input_index - Input mix buffer index for this command.
+ * Added to buffer_offset.
+ * @param output_index - Output mix buffer index for this command.
+ * Added to buffer_offset.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param update_count - Number of samples to write back to the game as updated, can be 0.
+ * @param count_max - Maximum number of samples to read or write.
+ * @param write_offset - Current read or write offset within the buffer.
+ */
+ void GenerateAuxCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
+ s16 output_index, s16 buffer_offset, u32 update_count, u32 count_max,
+ u32 write_offset);
+
+ /**
+ * Generate a device sink command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param sink_info - The sink_info to generate this command from.
+ * @param session_id - System session id this command is generated from.
+ * @param samples_buffer - The buffer to be sent to the sink if upsampling is not used.
+ */
+ void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info,
+ u32 session_id, std::span<s32> samples_buffer);
+
+ /**
+ * Generate a circular buffer sink command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param sink_info - The sink_info to generate this command from.
+ * @param buffer_offset - Base mix buffer offset to use.
+ */
+ void GenerateCircularBufferSinkCommand(s32 node_id, SinkInfoBase& sink_info, s16 buffer_offset);
+
+ /**
+ * Generate a reverb command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - Reverb effect info to generate this command from.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param long_size_pre_delay_supported - Should a longer pre-delay time be used before reverb
+ * begins?
+ */
+ void GenerateReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
+ bool long_size_pre_delay_supported);
+
+ /**
+ * Generate an I3DL2 reverb command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - I3DL2Reverb effect info to generate this command from.
+ * @param buffer_offset - Base mix buffer offset to use.
+ */
+ void GenerateI3dl2ReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
+
+ /**
+ * Generate a performance command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param state - State of the performance.
+ * @param entry_addresses - The addresses to be filled in by the AudioRenderer.
+ */
+ void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
+ const PerformanceEntryAddresses& entry_addresses);
+
+ /**
+ * Generate a clear mix command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ */
+ void GenerateClearMixCommand(s32 node_id);
+
+ /**
+ * Generate a copy mix command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - BiquadFilter effect info to generate this command from.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param channel - Index to the effect's parameters input indexes for this command.
+ */
+ void GenerateCopyMixBufferCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
+ s8 channel);
+
+ /**
+ * Generate a light limiter version 1 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param parameter - Effect parameter to generate from.
+ * @param state - State used by the AudioRenderer between commands.
+ * @param enabled - Is this command enabled?
+ * @param workbuffer - Game-supplied memory for the state.
+ */
+ void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
+ const LightLimiterInfo::ParameterVersion1& parameter,
+ const LightLimiterInfo::State& state, bool enabled,
+ CpuAddr workbuffer);
+
+ /**
+ * Generate a light limiter version 2 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param parameter - Effect parameter to generate from.
+ * @param statistics - Statistics reported by the AudioRenderer on the limiter's state.
+ * @param state - State used by the AudioRenderer between commands.
+ * @param enabled - Is this command enabled?
+ * @param workbuffer - Game-supplied memory for the state.
+ */
+ void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
+ const LightLimiterInfo::ParameterVersion2& parameter,
+ const LightLimiterInfo::StatisticsInternal& statistics,
+ const LightLimiterInfo::State& state, bool enabled,
+ CpuAddr workbuffer);
+
+ /**
+ * Generate a multitap biquad filter command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_info - The voice info this command takes biquad parameters from.
+ * @param voice_state - Used by the AudioRenderer to track previous samples.
+ * @param buffer_count - Number of active mix buffers,
+ * command will generate at this index + channel.
+ * @param channel - Channel index for this filter to work on.
+ */
+ void GenerateMultitapBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state, s16 buffer_count,
+ s8 channel);
+
+ /**
+ * Generate a capture command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - Capture effect info to generate this command from.
+ * @param input_index - Input mix buffer index for this command.
+ * Added to buffer_offset.
+ * @param output_index - Output mix buffer index for this command (unused).
+ * Added to buffer_offset.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param update_count - Number of samples to write back to the game as updated, can be 0.
+ * @param count_max - Maximum number of samples to read or write.
+ * @param write_offset - Current read or write offset within the buffer.
+ */
+ void GenerateCaptureCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
+ s16 output_index, s16 buffer_offset, u32 update_count,
+ u32 count_max, u32 write_offset);
+
+ /**
+ * Generate a compressor command, adding it to the command list.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info - Capture effect info to generate this command from.
+ * @param node_id - Node id of the voice this command is generated for.
+ */
+ void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
+
+ /// Command list buffer generated commands will be added to
+ std::span<u8> command_list{};
+ /// Input sample count, unused
+ u32 sample_count{};
+ /// Input sample rate, unused
+ u32 sample_rate{};
+ /// Current size of the command buffer
+ u64 size{};
+ /// Current number of commands added
+ u32 count{};
+ /// Current estimated processing time for all commands
+ u32 estimated_process_time{};
+ /// Used for mapping buffers for the AudioRenderer
+ MemoryPoolInfo* memory_pool{};
+ /// Used for estimating command process times
+ ICommandProcessingTimeEstimator* time_estimator{};
+ /// Used to check which rendering features are currently enabled
+ BehaviorInfo* behavior{};
+
+private:
+ template <typename T, CommandId Id>
+ T& GenerateStart(const s32 node_id);
+ template <typename T>
+ void GenerateEnd(T& cmd);
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_generator.cpp b/src/audio_core/renderer/command/command_generator.cpp
new file mode 100644
index 000000000..2ea50d128
--- /dev/null
+++ b/src/audio_core/renderer/command/command_generator.cpp
@@ -0,0 +1,796 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/command/command_buffer.h"
+#include "audio_core/renderer/command/command_generator.h"
+#include "audio_core/renderer/command/command_list_header.h"
+#include "audio_core/renderer/effect/aux_.h"
+#include "audio_core/renderer/effect/biquad_filter.h"
+#include "audio_core/renderer/effect/buffer_mixer.h"
+#include "audio_core/renderer/effect/capture.h"
+#include "audio_core/renderer/effect/effect_context.h"
+#include "audio_core/renderer/effect/light_limiter.h"
+#include "audio_core/renderer/mix/mix_context.h"
+#include "audio_core/renderer/performance/detail_aspect.h"
+#include "audio_core/renderer/performance/entry_aspect.h"
+#include "audio_core/renderer/sink/device_sink_info.h"
+#include "audio_core/renderer/sink/sink_context.h"
+#include "audio_core/renderer/splitter/splitter_context.h"
+#include "audio_core/renderer/voice/voice_context.h"
+#include "common/alignment.h"
+
+namespace AudioCore::AudioRenderer {
+
+CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_,
+ const CommandListHeader& command_list_header_,
+ const AudioRendererSystemContext& render_context_,
+ VoiceContext& voice_context_, MixContext& mix_context_,
+ EffectContext& effect_context_, SinkContext& sink_context_,
+ SplitterContext& splitter_context_,
+ PerformanceManager* performance_manager_)
+ : command_buffer{command_buffer_}, command_header{command_list_header_},
+ render_context{render_context_}, voice_context{voice_context_}, mix_context{mix_context_},
+ effect_context{effect_context_}, sink_context{sink_context_},
+ splitter_context{splitter_context_}, performance_manager{performance_manager_} {
+ command_buffer.GenerateClearMixCommand(InvalidNodeId);
+}
+
+void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info,
+ const VoiceState& voice_state, const s8 channel) {
+ if (voice_info.mix_id == UnusedMixId) {
+ if (voice_info.splitter_id != UnusedSplitterId) {
+ auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)};
+ u32 dest_id{0};
+ while (destination != nullptr) {
+ if (destination->IsConfigured()) {
+ auto mix_id{destination->GetMixId()};
+ if (mix_id < mix_context.GetCount()) {
+ auto mix_info{mix_context.GetInfo(mix_id)};
+ command_buffer.GenerateDepopPrepareCommand(
+ voice_info.node_id, voice_state, render_context.depop_buffer,
+ mix_info->buffer_count, mix_info->buffer_offset,
+ voice_info.was_playing);
+ }
+ }
+ dest_id++;
+ destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id);
+ }
+ }
+ } else {
+ auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
+ command_buffer.GenerateDepopPrepareCommand(
+ voice_info.node_id, voice_state, render_context.depop_buffer, mix_info->buffer_count,
+ mix_info->buffer_offset, voice_info.was_playing);
+ }
+
+ if (voice_info.was_playing) {
+ return;
+ }
+
+ if (render_context.behavior->IsWaveBufferVer2Supported()) {
+ switch (voice_info.sample_format) {
+ case SampleFormat::PcmInt16:
+ command_buffer.GeneratePcmInt16Version2Command(
+ voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
+ channel);
+ break;
+ case SampleFormat::PcmFloat:
+ command_buffer.GeneratePcmFloatVersion2Command(
+ voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
+ channel);
+ break;
+ case SampleFormat::Adpcm:
+ command_buffer.GenerateAdpcmVersion2Command(voice_info.node_id, voice_info, voice_state,
+ render_context.mix_buffer_count, channel);
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
+ static_cast<u32>(voice_info.sample_format));
+ break;
+ }
+ } else {
+ switch (voice_info.sample_format) {
+ case SampleFormat::PcmInt16:
+ command_buffer.GeneratePcmInt16Version1Command(
+ voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
+ render_context.mix_buffer_count, channel);
+ break;
+ case SampleFormat::PcmFloat:
+ command_buffer.GeneratePcmFloatVersion1Command(
+ voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
+ render_context.mix_buffer_count, channel);
+ break;
+ case SampleFormat::Adpcm:
+ command_buffer.GenerateAdpcmVersion1Command(
+ voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
+ render_context.mix_buffer_count, channel);
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
+ static_cast<u32>(voice_info.sample_format));
+ break;
+ }
+ }
+}
+
+void CommandGenerator::GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
+ std::span<const f32> prev_mix_volumes,
+ const VoiceState& voice_state, s16 output_index,
+ const s16 buffer_count, const s16 input_index,
+ const s32 node_id) {
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ if (buffer_count > 8) {
+ const auto prev_samples{render_context.memory_pool_info->Translate(
+ CpuAddr(voice_state.previous_samples.data()), buffer_count * sizeof(s32))};
+ command_buffer.GenerateMixRampGroupedCommand(node_id, buffer_count, input_index,
+ output_index, mix_volumes, prev_mix_volumes,
+ prev_samples, precision);
+ } else {
+ for (s16 i = 0; i < buffer_count; i++, output_index++) {
+ const auto prev_samples{render_context.memory_pool_info->Translate(
+ CpuAddr(&voice_state.previous_samples[i]), sizeof(s32))};
+
+ command_buffer.GenerateMixRampCommand(node_id, buffer_count, input_index, output_index,
+ mix_volumes[i], prev_mix_volumes[i], prev_samples,
+ precision);
+ }
+ }
+}
+
+void CommandGenerator::GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel,
+ const s32 node_id) {
+ const bool both_biquads_enabled{voice_info.biquads[0].enabled && voice_info.biquads[1].enabled};
+ const auto use_float_processing{render_context.behavior->UseBiquadFilterFloatProcessing()};
+
+ if (both_biquads_enabled && render_context.behavior->UseMultiTapBiquadFilterProcessing() &&
+ use_float_processing) {
+ command_buffer.GenerateMultitapBiquadFilterCommand(node_id, voice_info, voice_state,
+ buffer_count, channel);
+ } else {
+ for (u32 i = 0; i < MaxBiquadFilters; i++) {
+ if (voice_info.biquads[i].enabled) {
+ command_buffer.GenerateBiquadFilterCommand(node_id, voice_info, voice_state,
+ buffer_count, channel, i,
+ use_float_processing);
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) {
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ for (s8 channel = 0; channel < voice_info.channel_count; channel++) {
+ const auto resource_id{voice_info.channel_resource_ids[channel]};
+ auto& voice_state{voice_context.GetDspSharedState(resource_id)};
+ auto& channel_resource{voice_context.GetChannelResource(resource_id)};
+
+ PerformanceDetailType detail_type{PerformanceDetailType::Invalid};
+ switch (voice_info.sample_format) {
+ case SampleFormat::PcmInt16:
+ detail_type = PerformanceDetailType::Unk1;
+ break;
+ case SampleFormat::PcmFloat:
+ detail_type = PerformanceDetailType::Unk10;
+ break;
+ default:
+ detail_type = PerformanceDetailType::Unk2;
+ break;
+ }
+
+ DetailAspect data_source_detail(*this, PerformanceEntryType::Voice, voice_info.node_id,
+ detail_type);
+ GenerateDataSourceCommand(voice_info, voice_state, channel);
+
+ if (data_source_detail.initialized) {
+ command_buffer.GeneratePerformanceCommand(data_source_detail.node_id,
+ PerformanceState::Stop,
+ data_source_detail.performance_entry_address);
+ }
+
+ if (voice_info.was_playing) {
+ voice_info.prev_volume = 0.0f;
+ continue;
+ }
+
+ if (!voice_info.HasAnyConnection()) {
+ continue;
+ }
+
+ DetailAspect biquad_detail_aspect(*this, PerformanceEntryType::Voice, voice_info.node_id,
+ PerformanceDetailType::Unk4);
+ GenerateBiquadFilterCommandForVoice(
+ voice_info, voice_state, render_context.mix_buffer_count, channel, voice_info.node_id);
+
+ if (biquad_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ biquad_detail_aspect.node_id, PerformanceState::Stop,
+ biquad_detail_aspect.performance_entry_address);
+ }
+
+ DetailAspect volume_ramp_detail_aspect(*this, PerformanceEntryType::Voice,
+ voice_info.node_id, PerformanceDetailType::Unk3);
+ command_buffer.GenerateVolumeRampCommand(
+ voice_info.node_id, voice_info, render_context.mix_buffer_count + channel, precision);
+ if (volume_ramp_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ volume_ramp_detail_aspect.node_id, PerformanceState::Stop,
+ volume_ramp_detail_aspect.performance_entry_address);
+ }
+
+ voice_info.prev_volume = voice_info.volume;
+
+ if (voice_info.mix_id == UnusedMixId) {
+ if (voice_info.splitter_id != UnusedSplitterId) {
+ auto i{channel};
+ auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, i)};
+ while (destination != nullptr) {
+ if (destination->IsConfigured()) {
+ const auto mix_id{destination->GetMixId()};
+ if (mix_id < mix_context.GetCount() &&
+ static_cast<s32>(mix_id) != UnusedSplitterId) {
+ auto mix_info{mix_context.GetInfo(mix_id)};
+ GenerateVoiceMixCommand(
+ destination->GetMixVolume(), destination->GetMixVolumePrev(),
+ voice_state, mix_info->buffer_offset, mix_info->buffer_count,
+ render_context.mix_buffer_count + channel, voice_info.node_id);
+ destination->MarkAsNeedToUpdateInternalState();
+ }
+ }
+ i += voice_info.channel_count;
+ destination = splitter_context.GetDesintationData(voice_info.splitter_id, i);
+ }
+ }
+ } else {
+ DetailAspect volume_mix_detail_aspect(*this, PerformanceEntryType::Voice,
+ voice_info.node_id, PerformanceDetailType::Unk3);
+ auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
+ GenerateVoiceMixCommand(channel_resource.mix_volumes, channel_resource.prev_mix_volumes,
+ voice_state, mix_info->buffer_offset, mix_info->buffer_count,
+ render_context.mix_buffer_count + channel, voice_info.node_id);
+ if (volume_mix_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ volume_mix_detail_aspect.node_id, PerformanceState::Stop,
+ volume_mix_detail_aspect.performance_entry_address);
+ }
+
+ channel_resource.prev_mix_volumes = channel_resource.mix_volumes;
+ }
+ voice_info.biquad_initialized[0] = voice_info.biquads[0].enabled;
+ voice_info.biquad_initialized[1] = voice_info.biquads[1].enabled;
+ }
+}
+
+void CommandGenerator::GenerateVoiceCommands() {
+ const auto voice_count{voice_context.GetCount()};
+
+ for (u32 i = 0; i < voice_count; i++) {
+ auto sorted_info{voice_context.GetSortedInfo(i)};
+
+ if (sorted_info->ShouldSkip() || !sorted_info->UpdateForCommandGeneration(voice_context)) {
+ continue;
+ }
+
+ EntryAspect voice_entry_aspect(*this, PerformanceEntryType::Voice, sorted_info->node_id);
+
+ GenerateVoiceCommand(*sorted_info);
+
+ if (voice_entry_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(voice_entry_aspect.node_id,
+ PerformanceState::Stop,
+ voice_entry_aspect.performance_entry_address);
+ }
+ }
+
+ splitter_context.UpdateInternalState();
+}
+
+void CommandGenerator::GenerateBufferMixerCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info, const s32 node_id) {
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ if (effect_info.IsEnabled()) {
+ const auto& parameter{
+ *reinterpret_cast<BufferMixerInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ for (u32 i = 0; i < parameter.mix_count; i++) {
+ if (parameter.volumes[i] != 0.0f) {
+ command_buffer.GenerateMixCommand(node_id, buffer_offset + parameter.inputs[i],
+ buffer_offset + parameter.outputs[i],
+ buffer_offset, parameter.volumes[i], precision);
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateDelayCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
+ const s32 node_id) {
+ command_buffer.GenerateDelayCommand(node_id, effect_info, buffer_offset);
+}
+
+void CommandGenerator::GenerateReverbCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
+ const s32 node_id,
+ const bool long_size_pre_delay_supported) {
+ command_buffer.GenerateReverbCommand(node_id, effect_info, buffer_offset,
+ long_size_pre_delay_supported);
+}
+
+void CommandGenerator::GenerateI3dl2ReverbEffectCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info,
+ const s32 node_id) {
+ command_buffer.GenerateI3dl2ReverbCommand(node_id, effect_info, buffer_offset);
+}
+
+void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
+ const s32 node_id) {
+
+ if (effect_info.IsEnabled()) {
+ effect_info.GetWorkbuffer(0);
+ effect_info.GetWorkbuffer(1);
+ }
+
+ if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) {
+ const auto& parameter{
+ *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ auto channel_index{parameter.mix_buffer_count - 1};
+ u32 write_offset{0};
+ for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
+ auto new_update_count{command_header.sample_count + write_offset};
+ const auto update_count{channel_index > 0 ? 0 : new_update_count};
+ command_buffer.GenerateAuxCommand(node_id, effect_info, parameter.inputs[i],
+ parameter.outputs[i], buffer_offset, update_count,
+ parameter.count_max, write_offset);
+ write_offset = new_update_count;
+ }
+ }
+}
+
+void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info,
+ const s32 node_id) {
+ const auto& parameter{
+ *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ if (effect_info.IsEnabled()) {
+ bool needs_init{false};
+
+ switch (parameter.state) {
+ case EffectInfoBase::ParameterState::Initialized:
+ needs_init = true;
+ break;
+ case EffectInfoBase::ParameterState::Updating:
+ case EffectInfoBase::ParameterState::Updated:
+ if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) {
+ needs_init = false;
+ } else {
+ needs_init = parameter.state == EffectInfoBase::ParameterState::Updating;
+ }
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}",
+ static_cast<u32>(parameter.state));
+ break;
+ }
+
+ for (s8 channel = 0; channel < parameter.channel_count; channel++) {
+ command_buffer.GenerateBiquadFilterCommand(
+ node_id, effect_info, buffer_offset, channel, needs_init,
+ render_context.behavior->UseBiquadFilterFloatProcessing());
+ }
+ } else {
+ for (s8 channel = 0; channel < parameter.channel_count; channel++) {
+ command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset,
+ channel);
+ }
+ }
+}
+
+void CommandGenerator::GenerateLightLimiterEffectCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info,
+ const s32 node_id,
+ const u32 effect_index) {
+
+ const auto& state{*reinterpret_cast<LightLimiterInfo::State*>(effect_info.GetStateBuffer())};
+
+ if (render_context.behavior->IsEffectInfoVersion2Supported()) {
+ const auto& parameter{
+ *reinterpret_cast<LightLimiterInfo::ParameterVersion2*>(effect_info.GetParameter())};
+ const auto& result_state{*reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(
+ &effect_context.GetDspSharedResultState(effect_index))};
+ command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, result_state,
+ state, effect_info.IsEnabled(),
+ effect_info.GetWorkbuffer(-1));
+ } else {
+ const auto& parameter{
+ *reinterpret_cast<LightLimiterInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, state,
+ effect_info.IsEnabled(),
+ effect_info.GetWorkbuffer(-1));
+ }
+}
+
+void CommandGenerator::GenerateCaptureCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
+ const s32 node_id) {
+ if (effect_info.IsEnabled()) {
+ effect_info.GetWorkbuffer(0);
+ }
+
+ if (effect_info.GetSendBuffer()) {
+ const auto& parameter{
+ *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ auto channel_index{parameter.mix_buffer_count - 1};
+ u32 write_offset{0};
+ for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
+ auto new_update_count{command_header.sample_count + write_offset};
+ const auto update_count{channel_index > 0 ? 0 : new_update_count};
+ command_buffer.GenerateCaptureCommand(node_id, effect_info, parameter.inputs[i],
+ parameter.outputs[i], buffer_offset, update_count,
+ parameter.count_max, write_offset);
+ write_offset = new_update_count;
+ }
+ }
+}
+
+void CommandGenerator::GenerateCompressorCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info, const s32 node_id) {
+ command_buffer.GenerateCompressorCommand(buffer_offset, effect_info, node_id);
+}
+
+void CommandGenerator::GenerateEffectCommand(MixInfo& mix_info) {
+ const auto effect_count{effect_context.GetCount()};
+ for (u32 i = 0; i < effect_count; i++) {
+ const auto effect_index{mix_info.effect_order_buffer[i]};
+ if (effect_index == -1) {
+ break;
+ }
+
+ auto& effect_info = effect_context.GetInfo(effect_index);
+ if (effect_info.ShouldSkip()) {
+ continue;
+ }
+
+ const auto entry_type{mix_info.mix_id == FinalMixId ? PerformanceEntryType::FinalMix
+ : PerformanceEntryType::SubMix};
+
+ switch (effect_info.GetType()) {
+ case EffectInfoBase::Type::Mix: {
+ DetailAspect mix_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk5);
+ GenerateBufferMixerCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (mix_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ mix_detail_aspect.node_id, PerformanceState::Stop,
+ mix_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Aux: {
+ DetailAspect aux_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk7);
+ GenerateAuxCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (aux_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ aux_detail_aspect.node_id, PerformanceState::Stop,
+ aux_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Delay: {
+ DetailAspect delay_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk6);
+ GenerateDelayCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (delay_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ delay_detail_aspect.node_id, PerformanceState::Stop,
+ delay_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Reverb: {
+ DetailAspect reverb_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk8);
+ GenerateReverbCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
+ render_context.behavior->IsLongSizePreDelaySupported());
+ if (reverb_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ reverb_detail_aspect.node_id, PerformanceState::Stop,
+ reverb_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::I3dl2Reverb: {
+ DetailAspect i3dl2_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk9);
+ GenerateI3dl2ReverbEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (i3dl2_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ i3dl2_detail_aspect.node_id, PerformanceState::Stop,
+ i3dl2_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::BiquadFilter: {
+ DetailAspect biquad_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk4);
+ GenerateBiquadFilterEffectCommand(mix_info.buffer_offset, effect_info,
+ mix_info.node_id);
+ if (biquad_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ biquad_detail_aspect.node_id, PerformanceState::Stop,
+ biquad_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::LightLimiter: {
+ DetailAspect light_limiter_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk11);
+ GenerateLightLimiterEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
+ effect_index);
+ if (light_limiter_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ light_limiter_detail_aspect.node_id, PerformanceState::Stop,
+ light_limiter_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Capture: {
+ DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk12);
+ GenerateCaptureCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (capture_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ capture_detail_aspect.node_id, PerformanceState::Stop,
+ capture_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Compressor: {
+ DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk13);
+ GenerateCompressorCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (capture_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ capture_detail_aspect.node_id, PerformanceState::Stop,
+ capture_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid effect type {}",
+ static_cast<u32>(effect_info.GetType()));
+ break;
+ }
+
+ effect_info.UpdateForCommandGeneration();
+ }
+}
+
+void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) {
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ if (!mix_info.HasAnyConnection()) {
+ return;
+ }
+
+ if (mix_info.dst_mix_id == UnusedMixId) {
+ if (mix_info.dst_splitter_id != UnusedSplitterId) {
+ s16 dest_id{0};
+ auto destination{
+ splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id)};
+ while (destination != nullptr) {
+ if (destination->IsConfigured()) {
+ auto splitter_mix_id{destination->GetMixId()};
+ if (splitter_mix_id < mix_context.GetCount()) {
+ auto splitter_mix_info{mix_context.GetInfo(splitter_mix_id)};
+ const s16 input_index{static_cast<s16>(mix_info.buffer_offset +
+ (dest_id % mix_info.buffer_count))};
+ for (s16 i = 0; i < splitter_mix_info->buffer_count; i++) {
+ auto volume{mix_info.volume * destination->GetMixVolume(i)};
+ if (volume != 0.0f) {
+ command_buffer.GenerateMixCommand(
+ mix_info.node_id, input_index,
+ splitter_mix_info->buffer_offset + i, mix_info.buffer_offset,
+ volume, precision);
+ }
+ }
+ }
+ }
+ dest_id++;
+ destination =
+ splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id);
+ }
+ }
+ } else {
+ auto dest_mix_info{mix_context.GetInfo(mix_info.dst_mix_id)};
+ for (s16 i = 0; i < mix_info.buffer_count; i++) {
+ for (s16 j = 0; j < dest_mix_info->buffer_count; j++) {
+ auto volume{mix_info.volume * mix_info.mix_volumes[i][j]};
+ if (volume != 0.0f) {
+ command_buffer.GenerateMixCommand(mix_info.node_id, mix_info.buffer_offset + i,
+ dest_mix_info->buffer_offset + j,
+ mix_info.buffer_offset, volume, precision);
+ }
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateSubMixCommand(MixInfo& mix_info) {
+ command_buffer.GenerateDepopForMixBuffersCommand(mix_info.node_id, mix_info,
+ render_context.depop_buffer);
+ GenerateEffectCommand(mix_info);
+
+ DetailAspect mix_detail_aspect(*this, PerformanceEntryType::SubMix, mix_info.node_id,
+ PerformanceDetailType::Unk5);
+
+ GenerateMixCommands(mix_info);
+
+ if (mix_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(mix_detail_aspect.node_id, PerformanceState::Stop,
+ mix_detail_aspect.performance_entry_address);
+ }
+}
+
+void CommandGenerator::GenerateSubMixCommands() {
+ const auto submix_count{mix_context.GetCount()};
+ for (s32 i = 0; i < submix_count; i++) {
+ auto sorted_info{mix_context.GetSortedInfo(i)};
+ if (!sorted_info->in_use || sorted_info->mix_id == FinalMixId) {
+ continue;
+ }
+
+ EntryAspect submix_entry_aspect(*this, PerformanceEntryType::SubMix, sorted_info->node_id);
+
+ GenerateSubMixCommand(*sorted_info);
+
+ if (submix_entry_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ submix_entry_aspect.node_id, PerformanceState::Stop,
+ submix_entry_aspect.performance_entry_address);
+ }
+ }
+}
+
+void CommandGenerator::GenerateFinalMixCommand() {
+ auto& final_mix_info{*mix_context.GetFinalMixInfo()};
+
+ command_buffer.GenerateDepopForMixBuffersCommand(final_mix_info.node_id, final_mix_info,
+ render_context.depop_buffer);
+ GenerateEffectCommand(final_mix_info);
+
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ for (s16 i = 0; i < final_mix_info.buffer_count; i++) {
+ DetailAspect volume_aspect(*this, PerformanceEntryType::FinalMix, final_mix_info.node_id,
+ PerformanceDetailType::Unk3);
+ command_buffer.GenerateVolumeCommand(final_mix_info.node_id, final_mix_info.buffer_offset,
+ i, final_mix_info.volume, precision);
+ if (volume_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(volume_aspect.node_id, PerformanceState::Stop,
+ volume_aspect.performance_entry_address);
+ }
+ }
+}
+
+void CommandGenerator::GenerateFinalMixCommands() {
+ auto final_mix_info{mix_context.GetFinalMixInfo()};
+ EntryAspect final_mix_entry(*this, PerformanceEntryType::FinalMix, final_mix_info->node_id);
+ GenerateFinalMixCommand();
+ if (final_mix_entry.initialized) {
+ command_buffer.GeneratePerformanceCommand(final_mix_entry.node_id, PerformanceState::Stop,
+ final_mix_entry.performance_entry_address);
+ }
+}
+
+void CommandGenerator::GenerateSinkCommands() {
+ const auto sink_count{sink_context.GetCount()};
+
+ for (u32 i = 0; i < sink_count; i++) {
+ auto sink_info{sink_context.GetInfo(i)};
+ if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::DeviceSink) {
+ auto state{reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info->GetState())};
+ if (command_header.sample_rate != TargetSampleRate &&
+ state->upsampler_info == nullptr) {
+ auto device_state{sink_info->GetDeviceState()};
+ device_state->upsampler_info = render_context.upsampler_manager->Allocate();
+ }
+
+ EntryAspect device_sink_entry(*this, PerformanceEntryType::Sink,
+ sink_info->GetNodeId());
+ auto final_mix{mix_context.GetFinalMixInfo()};
+ GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
+
+ if (device_sink_entry.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ device_sink_entry.node_id, PerformanceState::Stop,
+ device_sink_entry.performance_entry_address);
+ }
+ }
+ }
+
+ for (u32 i = 0; i < sink_count; i++) {
+ auto sink_info{sink_context.GetInfo(i)};
+ if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::CircularBufferSink) {
+ EntryAspect circular_buffer_entry(*this, PerformanceEntryType::Sink,
+ sink_info->GetNodeId());
+ auto final_mix{mix_context.GetFinalMixInfo()};
+ GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
+
+ if (circular_buffer_entry.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ circular_buffer_entry.node_id, PerformanceState::Stop,
+ circular_buffer_entry.performance_entry_address);
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
+ if (sink_info.ShouldSkip()) {
+ return;
+ }
+
+ switch (sink_info.GetType()) {
+ case SinkInfoBase::Type::DeviceSink:
+ GenerateDeviceSinkCommand(buffer_offset, sink_info);
+ break;
+
+ case SinkInfoBase::Type::CircularBufferSink:
+ command_buffer.GenerateCircularBufferSinkCommand(sink_info.GetNodeId(), sink_info,
+ buffer_offset);
+ break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(sink_info.GetType()));
+ break;
+ }
+
+ sink_info.UpdateForCommandGeneration();
+}
+
+void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
+ auto& parameter{
+ *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())};
+ auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())};
+
+ if (render_context.channels == 2 && parameter.downmix_enabled) {
+ command_buffer.GenerateDownMix6chTo2chCommand(InvalidNodeId, parameter.inputs,
+ buffer_offset, parameter.downmix_coeff);
+ }
+
+ if (state.upsampler_info != nullptr) {
+ command_buffer.GenerateUpsampleCommand(
+ InvalidNodeId, buffer_offset, *state.upsampler_info, parameter.input_count,
+ parameter.inputs, command_header.buffer_count, command_header.sample_count,
+ command_header.sample_rate);
+ }
+
+ command_buffer.GenerateDeviceSinkCommand(InvalidNodeId, buffer_offset, sink_info,
+ render_context.session_id,
+ command_header.samples_buffer);
+}
+
+void CommandGenerator::GeneratePerformanceCommand(
+ s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses) {
+ command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_generator.h b/src/audio_core/renderer/command/command_generator.h
new file mode 100644
index 000000000..b3cd7b408
--- /dev/null
+++ b/src/audio_core/renderer/command/command_generator.h
@@ -0,0 +1,349 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/command/commands.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+struct AudioRendererSystemContext;
+
+namespace AudioRenderer {
+class CommandBuffer;
+struct CommandListHeader;
+class VoiceContext;
+class MixContext;
+class EffectContext;
+class SplitterContext;
+class SinkContext;
+class BehaviorInfo;
+class VoiceInfo;
+struct VoiceState;
+class MixInfo;
+class SinkInfoBase;
+
+/**
+ * Generates all commands to build up a command list, which are sent to the AudioRender for
+ * processing.
+ */
+class CommandGenerator {
+public:
+ explicit CommandGenerator(CommandBuffer& command_buffer,
+ const CommandListHeader& command_list_header,
+ const AudioRendererSystemContext& render_context,
+ VoiceContext& voice_context, MixContext& mix_context,
+ EffectContext& effect_context, SinkContext& sink_context,
+ SplitterContext& splitter_context,
+ PerformanceManager* performance_manager);
+
+ /**
+ * Calculate the buffer size needed for commands.
+ *
+ * @param behavior - Used to check what features are enabled.
+ * @param params - Input rendering parameters for numbers of voices/mixes/sinks etc.
+ */
+ static u64 CalculateCommandBufferSize(const BehaviorInfo& behavior,
+ const AudioRendererParameterInternal& params) {
+ u64 size{0};
+
+ // Effects
+ size += params.effects * sizeof(EffectInfoBase);
+
+ // Voices
+ u64 voice_size{0};
+ if (behavior.IsWaveBufferVer2Supported()) {
+ voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion2Command),
+ sizeof(PcmInt16DataSourceVersion2Command)),
+ sizeof(PcmFloatDataSourceVersion2Command));
+ } else {
+ voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion1Command),
+ sizeof(PcmInt16DataSourceVersion1Command)),
+ sizeof(PcmFloatDataSourceVersion1Command));
+ }
+ voice_size += sizeof(BiquadFilterCommand) * MaxBiquadFilters;
+ voice_size += sizeof(VolumeRampCommand);
+ voice_size += sizeof(MixRampGroupedCommand);
+
+ size += params.voices * (params.splitter_infos * sizeof(DepopPrepareCommand) + voice_size);
+
+ // Sub mixes
+ size += sizeof(DepopForMixBuffersCommand) +
+ (sizeof(MixCommand) * MaxMixBuffers) * MaxMixBuffers;
+
+ // Final mix
+ size += sizeof(DepopForMixBuffersCommand) + sizeof(VolumeCommand) * MaxMixBuffers;
+
+ // Splitters
+ size += params.splitter_destinations * sizeof(MixRampCommand) * MaxMixBuffers;
+
+ // Sinks
+ size +=
+ params.sinks * std::max(sizeof(DeviceSinkCommand), sizeof(CircularBufferSinkCommand));
+
+ // Performance
+ size += (params.effects + params.voices + params.sinks + params.sub_mixes + 1 +
+ PerformanceManager::MaxDetailEntries) *
+ sizeof(PerformanceCommand);
+ return size;
+ }
+
+ /**
+ * Get the current command buffer used to generate commands.
+ *
+ * @return The command buffer.
+ */
+ CommandBuffer& GetCommandBuffer() {
+ return command_buffer;
+ }
+
+ /**
+ * Get the current performance manager,
+ *
+ * @return The performance manager. May be nullptr.
+ */
+ PerformanceManager* GetPerformanceManager() {
+ return performance_manager;
+ }
+
+ /**
+ * Generate a data source command.
+ * These are the basis for all audio output.
+ *
+ * @param voice_info - Generate the command from this voice.
+ * @param voice_state - State used by the AudioRenderer across calls.
+ * @param channel - Channel index to generate the command into.
+ */
+ void GenerateDataSourceCommand(VoiceInfo& voice_info, const VoiceState& voice_state,
+ s8 channel);
+
+ /**
+ * Generate voice mixing commands.
+ * These are used to mix buffers together, to mix one input to many outputs,
+ * and also used as copy commands to move data around and prevent it being accidentally
+ * overwritten, e.g by another data source command into the same channel.
+ *
+ * @param mix_volumes - Current volumes of the mix.
+ * @param prev_mix_volumes - Previous volumes of the mix.
+ * @param voice_state - State used by the AudioRenderer across calls.
+ * @param output_index - Output mix buffer index.
+ * @param buffer_count - Number of active mix buffers.
+ * @param input_index - Input mix buffer index.
+ * @param node_id - Node id of the voice this command is generated for.
+ */
+ void GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
+ std::span<const f32> prev_mix_volumes,
+ const VoiceState& voice_state, s16 output_index, s16 buffer_count,
+ s16 input_index, s32 node_id);
+
+ /**
+ * Generate a biquad filter command for a voice.
+ *
+ * @param voice_info - Voice info this command is generated from.
+ * @param voice_state - State used by the AudioRenderer across calls.
+ * @param buffer_count - Number of active mix buffers.
+ * @param channel - Channel index of this command.
+ * @param node_id - Node id of the voice this command is generated for.
+ */
+ void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state,
+ s16 buffer_count, s8 channel, s32 node_id);
+
+ /**
+ * Generate commands for a voice.
+ * Includes a data source, biquad filter, volume and mixing.
+ *
+ * @param voice_info - Voice info these commands are generated from.
+ */
+ void GenerateVoiceCommand(VoiceInfo& voice_info);
+
+ /**
+ * Generate commands for all voices.
+ */
+ void GenerateVoiceCommands();
+
+ /**
+ * Generate a mixing command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info_base - BufferMixer effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base,
+ s32 node_id);
+
+ /**
+ * Generate a delay effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info_base - Delay effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id);
+
+ /**
+ * Generate a reverb effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info_base - Reverb effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts.
+ */
+ void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id,
+ bool long_size_pre_delay_supported);
+
+ /**
+ * Generate an I3DL2 reverb effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info - I3DL2Reverb effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
+ s32 node_id);
+
+ /**
+ * Generate an aux effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info - Aux effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
+
+ /**
+ * Generate a biquad filter effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info - Aux effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
+ s32 node_id);
+
+ /**
+ * Generate a light limiter effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info - Limiter effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ * @param effect_index - Index for the statistics state.
+ */
+ void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
+ s32 node_id, u32 effect_index);
+
+ /**
+ * Generate a capture effect command.
+ * Writes a mix buffer back to game memory.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info - Capture effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
+
+ /**
+ * Generate a compressor effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info - Compressor effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
+
+ /**
+ * Generate all effect commands for a mix.
+ *
+ * @param mix_info - Mix to generate effects from.
+ */
+ void GenerateEffectCommand(MixInfo& mix_info);
+
+ /**
+ * Generate all mix commands.
+ *
+ * @param mix_info - Mix to generate effects from.
+ */
+ void GenerateMixCommands(MixInfo& mix_info);
+
+ /**
+ * Generate a submix command.
+ * Generates all effects and all mixing commands.
+ *
+ * @param mix_info - Mix to generate effects from.
+ */
+ void GenerateSubMixCommand(MixInfo& mix_info);
+
+ /**
+ * Generate all submix command.
+ */
+ void GenerateSubMixCommands();
+
+ /**
+ * Generate the final mix.
+ */
+ void GenerateFinalMixCommand();
+
+ /**
+ * Generate the final mix commands.
+ */
+ void GenerateFinalMixCommands();
+
+ /**
+ * Generate all sink commands.
+ */
+ void GenerateSinkCommands();
+
+ /**
+ * Generate a sink command.
+ * Sends samples out to the backend, or a game-supplied circular buffer.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param sink_info - Sink info to generate the commands from.
+ */
+ void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
+
+ /**
+ * Generate a device sink command.
+ * Sends samples out to the backend.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param sink_info - Sink info to generate the commands from.
+ */
+ void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
+
+ /**
+ * Generate a performance command.
+ * Used to report performance metrics of the AudioRenderer back to the game.
+ *
+ * @param node_id - Node ID of the mix this command is generated for
+ * @param state - Output state of the generated performance command
+ * @param entry_addresses - Addresses to be written
+ */
+ void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
+ const PerformanceEntryAddresses& entry_addresses);
+
+private:
+ /// Commands will be written by this buffer
+ CommandBuffer& command_buffer;
+ /// Header information for the commands generated
+ const CommandListHeader& command_header;
+ /// Various things to control generation
+ const AudioRendererSystemContext& render_context;
+ /// Used for generating voices
+ VoiceContext& voice_context;
+ /// Used for generating mixes
+ MixContext& mix_context;
+ /// Used for generating effects
+ EffectContext& effect_context;
+ /// Used for generating sinks
+ SinkContext& sink_context;
+ /// Used for generating submixes
+ SplitterContext& splitter_context;
+ /// Used for generating performance
+ PerformanceManager* performance_manager;
+};
+
+} // namespace AudioRenderer
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/command/command_list_header.h b/src/audio_core/renderer/command/command_list_header.h
new file mode 100644
index 000000000..988530b1f
--- /dev/null
+++ b/src/audio_core/renderer/command/command_list_header.h
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+struct CommandListHeader {
+ u64 buffer_size;
+ u32 command_count;
+ std::span<s32> samples_buffer;
+ s16 buffer_count;
+ u32 sample_count;
+ u32 sample_rate;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.cpp b/src/audio_core/renderer/command/command_processing_time_estimator.cpp
new file mode 100644
index 000000000..3091f587a
--- /dev/null
+++ b/src/audio_core/renderer/command/command_processing_time_estimator.cpp
@@ -0,0 +1,3620 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/command/command_processing_time_estimator.h"
+
+namespace AudioCore::AudioRenderer {
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ const PcmInt16DataSourceVersion1Command& command) const {
+ return static_cast<u32>(command.pitch * 0.25f * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ const PcmInt16DataSourceVersion2Command& command) const {
+ return static_cast<u32>(command.pitch * 0.25f * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const PcmFloatDataSourceVersion1Command& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const PcmFloatDataSourceVersion2Command& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ const AdpcmDataSourceVersion1Command& command) const {
+ return static_cast<u32>(command.pitch * 0.25f * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ const AdpcmDataSourceVersion2Command& command) const {
+ return static_cast<u32>(command.pitch * 0.25f * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const VolumeCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * 8.8f) * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const VolumeRampCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * 9.8f) * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const BiquadFilterCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * 58.0f) * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const MixCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * 10.0f) * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const MixRampCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * 14.4f) * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(const MixRampGroupedCommand& command) const {
+ u32 count{0};
+ for (u32 i = 0; i < command.buffer_count; i++) {
+ if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
+ count++;
+ }
+ }
+
+ return static_cast<u32>(((static_cast<f32>(sample_count) * 14.4f) * 1.2f) *
+ static_cast<f32>(count));
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const DepopPrepareCommand& command) const {
+ return 1080;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ const DepopForMixBuffersCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * 8.9f) *
+ static_cast<f32>(command.count));
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(const DelayCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * command.parameter.channel_count) *
+ 202.5f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const UpsampleCommand& command) const {
+ return 357915;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
+ return 16108;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(const AuxCommand& command) const {
+ if (command.enabled) {
+ return 15956;
+ }
+ return 3765;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const DeviceSinkCommand& command) const {
+ return 10042;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const CircularBufferSinkCommand& command) const {
+ return 55;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(const ReverbCommand& command) const {
+ if (command.enabled) {
+ return static_cast<u32>(
+ (command.parameter.channel_count * static_cast<f32>(sample_count) * 750) * 1.2f);
+ }
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(const I3dl2ReverbCommand& command) const {
+ if (command.enabled) {
+ return static_cast<u32>(
+ (command.parameter.channel_count * static_cast<f32>(sample_count) * 530) * 1.2f);
+ }
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const PerformanceCommand& command) const {
+ return 1454;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const ClearMixBufferCommand& command) const {
+ return static_cast<u32>(
+ ((static_cast<f32>(sample_count) * 0.83f) * static_cast<f32>(buffer_count)) * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const CopyMixBufferCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const LightLimiterVersion1Command& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const LightLimiterVersion2Command& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const CaptureCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const CompressorCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const PcmInt16DataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 749.269f +
+ 6138.94f);
+ case 240:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 1195.456f +
+ 7797.047f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const PcmInt16DataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 749.269f +
+ 6138.94f);
+ case 240:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 1195.456f +
+ 7797.047f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const PcmFloatDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 749.269f +
+ 6138.94f);
+ case 240:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 1195.456f +
+ 7797.047f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const PcmFloatDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 749.269f +
+ 6138.94f);
+ case 240:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 1195.456f +
+ 7797.047f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const AdpcmDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 2125.588f +
+ 9039.47f);
+ case 240:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 3564.088 +
+ 6225.471);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const AdpcmDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 2125.588f +
+ 9039.47f);
+ case 240:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 3564.088 +
+ 6225.471);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const VolumeCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1280.3f);
+ case 240:
+ return static_cast<u32>(1737.8f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const VolumeRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1403.9f);
+ case 240:
+ return static_cast<u32>(1884.3f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const BiquadFilterCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(4813.2f);
+ case 240:
+ return static_cast<u32>(6915.4f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const MixCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1342.2f);
+ case 240:
+ return static_cast<u32>(1833.2f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const MixRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1859.0f);
+ case 240:
+ return static_cast<u32>(2286.1f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(const MixRampGroupedCommand& command) const {
+ u32 count{0};
+ for (u32 i = 0; i < command.buffer_count; i++) {
+ if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
+ count++;
+ }
+ }
+
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) *
+ static_cast<f32>(count));
+ case 240:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) *
+ static_cast<f32>(count));
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const DepopPrepareCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(306.62f);
+ case 240:
+ return static_cast<u32>(293.22f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(762.96f);
+ case 240:
+ return static_cast<u32>(726.96f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DelayCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(41635.555f);
+ case 2:
+ return static_cast<u32>(97861.211f);
+ case 4:
+ return static_cast<u32>(192515.516f);
+ case 6:
+ return static_cast<u32>(301755.969f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(578.529f);
+ case 2:
+ return static_cast<u32>(663.064f);
+ case 4:
+ return static_cast<u32>(703.983f);
+ case 6:
+ return static_cast<u32>(760.032f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(8770.345f);
+ case 2:
+ return static_cast<u32>(25741.18f);
+ case 4:
+ return static_cast<u32>(47551.168f);
+ case 6:
+ return static_cast<u32>(81629.219f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(521.283f);
+ case 2:
+ return static_cast<u32>(585.396f);
+ case 4:
+ return static_cast<u32>(629.884f);
+ case 6:
+ return static_cast<u32>(713.57f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const UpsampleCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(292000.0f);
+ case 240:
+ return static_cast<u32>(0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(10009.0f);
+ case 240:
+ return static_cast<u32>(14577.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(const AuxCommand& command) const {
+ // Is this function bugged, returning the wrong time?
+ // Surely the larger time should be returned when enabled...
+ // CMP W8, #0
+ // MOV W8, #0x60; // 489.163f
+ // MOV W10, #0x64; // 7177.936f
+ // CSEL X8, X10, X8, EQ
+
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ return static_cast<u32>(489.163f);
+ }
+ return static_cast<u32>(7177.936f);
+ case 240:
+ if (command.enabled) {
+ return static_cast<u32>(485.562f);
+ }
+ return static_cast<u32>(9499.822f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DeviceSinkCommand& command) const {
+ switch (command.input_count) {
+ case 2:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9261.545f);
+ case 240:
+ return static_cast<u32>(9336.054f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 6:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9336.054f);
+ case 240:
+ return static_cast<u32>(9566.728f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const CircularBufferSinkCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 853.629f + 1284.517f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 1726.021f + 1369.683f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(const ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(97192.227f);
+ case 2:
+ return static_cast<u32>(103278.555f);
+ case 4:
+ return static_cast<u32>(109579.039f);
+ case 6:
+ return static_cast<u32>(115065.438f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(492.009f);
+ case 2:
+ return static_cast<u32>(554.463f);
+ case 4:
+ return static_cast<u32>(595.864f);
+ case 6:
+ return static_cast<u32>(656.617f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(136463.641f);
+ case 2:
+ return static_cast<u32>(145749.047f);
+ case 4:
+ return static_cast<u32>(154796.938f);
+ case 6:
+ return static_cast<u32>(161968.406f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(495.789f);
+ case 2:
+ return static_cast<u32>(527.163f);
+ case 4:
+ return static_cast<u32>(598.752f);
+ case 6:
+ return static_cast<u32>(666.025f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(const I3dl2ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(138836.484f);
+ case 2:
+ return static_cast<u32>(135428.172f);
+ case 4:
+ return static_cast<u32>(199181.844f);
+ case 6:
+ return static_cast<u32>(247345.906f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(718.704f);
+ case 2:
+ return static_cast<u32>(751.296f);
+ case 4:
+ return static_cast<u32>(797.464f);
+ case 6:
+ return static_cast<u32>(867.426f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(199952.734f);
+ case 2:
+ return static_cast<u32>(195199.5f);
+ case 4:
+ return static_cast<u32>(290575.875f);
+ case 6:
+ return static_cast<u32>(363494.531f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(534.24f);
+ case 2:
+ return static_cast<u32>(570.874f);
+ case 4:
+ return static_cast<u32>(660.933f);
+ case 6:
+ return static_cast<u32>(694.596f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const PerformanceCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(489.35f);
+ case 240:
+ return static_cast<u32>(491.18f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const ClearMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(buffer_count) * 260.4f + 139.65f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(buffer_count) * 668.85f + 193.2f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const CopyMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(836.32f);
+ case 240:
+ return static_cast<u32>(1000.9f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const LightLimiterVersion1Command& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const LightLimiterVersion2Command& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const CaptureCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const CompressorCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const PcmInt16DataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 427.52f +
+ 6329.442f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 710.143f +
+ 7853.286f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const PcmInt16DataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 427.52f +
+ 6329.442f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 371.876f +
+ 8049.415f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 423.43f +
+ 5062.659f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 710.143f +
+ 7853.286f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 610.487f +
+ 10138.842f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 676.722f +
+ 5810.962f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const PcmFloatDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 1672.026f +
+ 7681.211f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 2550.414f +
+ 9663.969f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const PcmFloatDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1672.026f +
+ 7681.211f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1672.982f +
+ 9038.011f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1673.216f +
+ 6027.577f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2550.414f +
+ 9663.969f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2522.303f +
+ 11758.571f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2537.061f +
+ 7369.309f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const AdpcmDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 1827.665f +
+ 7913.808f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 2756.372f +
+ 9736.702f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const AdpcmDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1827.665f +
+ 7913.808f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1829.285f +
+ 9607.814f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1824.609f +
+ 6517.476f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2756.372f +
+ 9736.702f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2731.308f +
+ 12154.379f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2732.152f +
+ 7929.442f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const VolumeCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1311.1f);
+ case 240:
+ return static_cast<u32>(1713.6f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const VolumeRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1425.3f);
+ case 240:
+ return static_cast<u32>(1700.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const BiquadFilterCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(4173.2f);
+ case 240:
+ return static_cast<u32>(5585.1f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const MixCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1402.8f);
+ case 240:
+ return static_cast<u32>(1853.2f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const MixRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1968.7f);
+ case 240:
+ return static_cast<u32>(2459.4f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(const MixRampGroupedCommand& command) const {
+ u32 count{0};
+ for (u32 i = 0; i < command.buffer_count; i++) {
+ if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
+ count++;
+ }
+ }
+
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) *
+ static_cast<f32>(count));
+ case 240:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) *
+ static_cast<f32>(count));
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const DepopPrepareCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(739.64f);
+ case 240:
+ return static_cast<u32>(910.97f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DelayCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(8929.042f);
+ case 2:
+ return static_cast<u32>(25500.75f);
+ case 4:
+ return static_cast<u32>(47759.617f);
+ case 6:
+ return static_cast<u32>(82203.07f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(1295.206f);
+ case 2:
+ return static_cast<u32>(1213.6f);
+ case 4:
+ return static_cast<u32>(942.028f);
+ case 6:
+ return static_cast<u32>(1001.553f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(11941.051f);
+ case 2:
+ return static_cast<u32>(37197.371f);
+ case 4:
+ return static_cast<u32>(69749.836f);
+ case 6:
+ return static_cast<u32>(120042.398f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(997.668f);
+ case 2:
+ return static_cast<u32>(977.634f);
+ case 4:
+ return static_cast<u32>(792.309f);
+ case 6:
+ return static_cast<u32>(875.427f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const UpsampleCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(312990.0f);
+ case 240:
+ return static_cast<u32>(0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9949.7f);
+ case 240:
+ return static_cast<u32>(14679.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(const AuxCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ return static_cast<u32>(7182.136f);
+ }
+ return static_cast<u32>(472.111f);
+ case 240:
+ if (command.enabled) {
+ return static_cast<u32>(9435.961f);
+ }
+ return static_cast<u32>(462.619f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DeviceSinkCommand& command) const {
+ switch (command.input_count) {
+ case 2:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(8979.956f);
+ case 240:
+ return static_cast<u32>(9221.907f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 6:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9177.903f);
+ case 240:
+ return static_cast<u32>(9725.897f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const CircularBufferSinkCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(const ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(81475.055f);
+ case 2:
+ return static_cast<u32>(84975.0f);
+ case 4:
+ return static_cast<u32>(91625.148f);
+ case 6:
+ return static_cast<u32>(95332.266f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(536.298f);
+ case 2:
+ return static_cast<u32>(588.798f);
+ case 4:
+ return static_cast<u32>(643.702f);
+ case 6:
+ return static_cast<u32>(705.999f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(120174.469f);
+ case 2:
+ return static_cast<u32>(125262.219f);
+ case 4:
+ return static_cast<u32>(135751.234f);
+ case 6:
+ return static_cast<u32>(141129.234f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(617.641f);
+ case 2:
+ return static_cast<u32>(659.536f);
+ case 4:
+ return static_cast<u32>(711.438f);
+ case 6:
+ return static_cast<u32>(778.071f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(const I3dl2ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(116754.984f);
+ case 2:
+ return static_cast<u32>(125912.055f);
+ case 4:
+ return static_cast<u32>(146336.031f);
+ case 6:
+ return static_cast<u32>(165812.656f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(735.0f);
+ case 2:
+ return static_cast<u32>(766.615f);
+ case 4:
+ return static_cast<u32>(834.067f);
+ case 6:
+ return static_cast<u32>(875.437f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(170292.344f);
+ case 2:
+ return static_cast<u32>(183875.625f);
+ case 4:
+ return static_cast<u32>(214696.188f);
+ case 6:
+ return static_cast<u32>(243846.766f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(508.473f);
+ case 2:
+ return static_cast<u32>(582.445f);
+ case 4:
+ return static_cast<u32>(626.419f);
+ case 6:
+ return static_cast<u32>(682.468f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const PerformanceCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(498.17f);
+ case 240:
+ return static_cast<u32>(489.42f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const ClearMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const CopyMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(842.59f);
+ case 240:
+ return static_cast<u32>(986.72f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const LightLimiterVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(21392.383f);
+ case 2:
+ return static_cast<u32>(26829.389f);
+ case 4:
+ return static_cast<u32>(32405.152f);
+ case 6:
+ return static_cast<u32>(52218.586f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(897.004f);
+ case 2:
+ return static_cast<u32>(931.549f);
+ case 4:
+ return static_cast<u32>(975.387f);
+ case 6:
+ return static_cast<u32>(1016.778f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30555.504f);
+ case 2:
+ return static_cast<u32>(39010.785f);
+ case 4:
+ return static_cast<u32>(48270.18f);
+ case 6:
+ return static_cast<u32>(76711.875f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(874.429f);
+ case 2:
+ return static_cast<u32>(921.553f);
+ case 4:
+ return static_cast<u32>(945.262f);
+ case 6:
+ return static_cast<u32>(992.26f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const LightLimiterVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(23308.928f);
+ case 2:
+ return static_cast<u32>(29954.062f);
+ case 4:
+ return static_cast<u32>(35807.477f);
+ case 6:
+ return static_cast<u32>(58339.773f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(21392.383f);
+ case 2:
+ return static_cast<u32>(26829.389f);
+ case 4:
+ return static_cast<u32>(32405.152f);
+ case 6:
+ return static_cast<u32>(52218.586f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(897.004f);
+ case 2:
+ return static_cast<u32>(931.549f);
+ case 4:
+ return static_cast<u32>(975.387f);
+ case 6:
+ return static_cast<u32>(1016.778f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(33526.121f);
+ case 2:
+ return static_cast<u32>(43549.355f);
+ case 4:
+ return static_cast<u32>(52190.281f);
+ case 6:
+ return static_cast<u32>(85526.516f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30555.504f);
+ case 2:
+ return static_cast<u32>(39010.785f);
+ case 4:
+ return static_cast<u32>(48270.18f);
+ case 6:
+ return static_cast<u32>(76711.875f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(874.429f);
+ case 2:
+ return static_cast<u32>(921.553f);
+ case 4:
+ return static_cast<u32>(945.262f);
+ case 6:
+ return static_cast<u32>(992.26f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const CaptureCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const CompressorCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const PcmInt16DataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 427.52f +
+ 6329.442f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 710.143f +
+ 7853.286f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const PcmInt16DataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 427.52f +
+ 6329.442f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 371.876f +
+ 8049.415f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 423.43f +
+ 5062.659f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 710.143f +
+ 7853.286f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 610.487f +
+ 10138.842f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 676.722f +
+ 5810.962f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const PcmFloatDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 1672.026f +
+ 7681.211f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 2550.414f +
+ 9663.969f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const PcmFloatDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1672.026f +
+ 7681.211f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1672.982f +
+ 9038.011f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1673.216f +
+ 6027.577f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2550.414f +
+ 9663.969f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2522.303f +
+ 11758.571f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2537.061f +
+ 7369.309f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const AdpcmDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 1827.665f +
+ 7913.808f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 2756.372f +
+ 9736.702f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const AdpcmDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1827.665f +
+ 7913.808f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1829.285f +
+ 9607.814f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1824.609f +
+ 6517.476f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2756.372f +
+ 9736.702f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2731.308f +
+ 12154.379f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2732.152f +
+ 7929.442f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const VolumeCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1311.1f);
+ case 240:
+ return static_cast<u32>(1713.6f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const VolumeRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1425.3f);
+ case 240:
+ return static_cast<u32>(1700.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const BiquadFilterCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(4173.2f);
+ case 240:
+ return static_cast<u32>(5585.1f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const MixCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1402.8f);
+ case 240:
+ return static_cast<u32>(1853.2f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const MixRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1968.7f);
+ case 240:
+ return static_cast<u32>(2459.4f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const MixRampGroupedCommand& command) const {
+ u32 count{0};
+ for (u32 i = 0; i < command.buffer_count; i++) {
+ if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
+ count++;
+ }
+ }
+
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) *
+ static_cast<f32>(count));
+ case 240:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) *
+ static_cast<f32>(count));
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const DepopPrepareCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(739.64f);
+ case 240:
+ return static_cast<u32>(910.97f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DelayCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(8929.042f);
+ case 2:
+ return static_cast<u32>(25500.75f);
+ case 4:
+ return static_cast<u32>(47759.617f);
+ case 6:
+ return static_cast<u32>(82203.07f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(1295.206f);
+ case 2:
+ return static_cast<u32>(1213.6f);
+ case 4:
+ return static_cast<u32>(942.028f);
+ case 6:
+ return static_cast<u32>(1001.553f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(11941.051f);
+ case 2:
+ return static_cast<u32>(37197.371f);
+ case 4:
+ return static_cast<u32>(69749.836f);
+ case 6:
+ return static_cast<u32>(120042.398f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(997.668f);
+ case 2:
+ return static_cast<u32>(977.634f);
+ case 4:
+ return static_cast<u32>(792.309f);
+ case 6:
+ return static_cast<u32>(875.427f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const UpsampleCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(312990.0f);
+ case 240:
+ return static_cast<u32>(0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9949.7f);
+ case 240:
+ return static_cast<u32>(14679.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const AuxCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ return static_cast<u32>(7182.136f);
+ }
+ return static_cast<u32>(472.111f);
+ case 240:
+ if (command.enabled) {
+ return static_cast<u32>(9435.961f);
+ }
+ return static_cast<u32>(462.619f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DeviceSinkCommand& command) const {
+ switch (command.input_count) {
+ case 2:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(8979.956f);
+ case 240:
+ return static_cast<u32>(9221.907f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 6:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9177.903f);
+ case 240:
+ return static_cast<u32>(9725.897f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const CircularBufferSinkCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(81475.055f);
+ case 2:
+ return static_cast<u32>(84975.0f);
+ case 4:
+ return static_cast<u32>(91625.148f);
+ case 6:
+ return static_cast<u32>(95332.266f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(536.298f);
+ case 2:
+ return static_cast<u32>(588.798f);
+ case 4:
+ return static_cast<u32>(643.702f);
+ case 6:
+ return static_cast<u32>(705.999f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(120174.469f);
+ case 2:
+ return static_cast<u32>(125262.219f);
+ case 4:
+ return static_cast<u32>(135751.234f);
+ case 6:
+ return static_cast<u32>(141129.234f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(617.641f);
+ case 2:
+ return static_cast<u32>(659.536f);
+ case 4:
+ return static_cast<u32>(711.438f);
+ case 6:
+ return static_cast<u32>(778.071f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const I3dl2ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(116754.984f);
+ case 2:
+ return static_cast<u32>(125912.055f);
+ case 4:
+ return static_cast<u32>(146336.031f);
+ case 6:
+ return static_cast<u32>(165812.656f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(735.0f);
+ case 2:
+ return static_cast<u32>(766.615f);
+ case 4:
+ return static_cast<u32>(834.067f);
+ case 6:
+ return static_cast<u32>(875.437f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(170292.344f);
+ case 2:
+ return static_cast<u32>(183875.625f);
+ case 4:
+ return static_cast<u32>(214696.188f);
+ case 6:
+ return static_cast<u32>(243846.766f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(508.473f);
+ case 2:
+ return static_cast<u32>(582.445f);
+ case 4:
+ return static_cast<u32>(626.419f);
+ case 6:
+ return static_cast<u32>(682.468f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const PerformanceCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(498.17f);
+ case 240:
+ return static_cast<u32>(489.42f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const ClearMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const CopyMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(842.59f);
+ case 240:
+ return static_cast<u32>(986.72f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const LightLimiterVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(21392.383f);
+ case 2:
+ return static_cast<u32>(26829.389f);
+ case 4:
+ return static_cast<u32>(32405.152f);
+ case 6:
+ return static_cast<u32>(52218.586f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(897.004f);
+ case 2:
+ return static_cast<u32>(931.549f);
+ case 4:
+ return static_cast<u32>(975.387f);
+ case 6:
+ return static_cast<u32>(1016.778f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30555.504f);
+ case 2:
+ return static_cast<u32>(39010.785f);
+ case 4:
+ return static_cast<u32>(48270.18f);
+ case 6:
+ return static_cast<u32>(76711.875f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(874.429f);
+ case 2:
+ return static_cast<u32>(921.553f);
+ case 4:
+ return static_cast<u32>(945.262f);
+ case 6:
+ return static_cast<u32>(992.26f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const LightLimiterVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(23308.928f);
+ case 2:
+ return static_cast<u32>(29954.062f);
+ case 4:
+ return static_cast<u32>(35807.477f);
+ case 6:
+ return static_cast<u32>(58339.773f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(21392.383f);
+ case 2:
+ return static_cast<u32>(26829.389f);
+ case 4:
+ return static_cast<u32>(32405.152f);
+ case 6:
+ return static_cast<u32>(52218.586f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(897.004f);
+ case 2:
+ return static_cast<u32>(931.549f);
+ case 4:
+ return static_cast<u32>(975.387f);
+ case 6:
+ return static_cast<u32>(1016.778f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(33526.121f);
+ case 2:
+ return static_cast<u32>(43549.355f);
+ case 4:
+ return static_cast<u32>(52190.281f);
+ case 6:
+ return static_cast<u32>(85526.516f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30555.504f);
+ case 2:
+ return static_cast<u32>(39010.785f);
+ case 4:
+ return static_cast<u32>(48270.18f);
+ case 6:
+ return static_cast<u32>(76711.875f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(874.429f);
+ case 2:
+ return static_cast<u32>(921.553f);
+ case 4:
+ return static_cast<u32>(945.262f);
+ case 6:
+ return static_cast<u32>(992.26f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(7424.5f);
+ case 240:
+ return static_cast<u32>(9730.4f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const CaptureCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ return static_cast<u32>(426.982f);
+ }
+ return static_cast<u32>(4261.005f);
+ case 240:
+ if (command.enabled) {
+ return static_cast<u32>(435.204f);
+ }
+ return static_cast<u32>(5858.265f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const CompressorCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const PcmInt16DataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 427.52f +
+ 6329.442f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 710.143f +
+ 7853.286f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const PcmInt16DataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 427.52f +
+ 6329.442f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 371.876f +
+ 8049.415f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 423.43f +
+ 5062.659f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 710.143f +
+ 7853.286f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 610.487f +
+ 10138.842f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 676.722f +
+ 5810.962f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const PcmFloatDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 1672.026f +
+ 7681.211f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 2550.414f +
+ 9663.969f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const PcmFloatDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1672.026f +
+ 7681.211f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1672.982f +
+ 9038.011f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1673.216f +
+ 6027.577f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2550.414f +
+ 9663.969f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2522.303f +
+ 11758.571f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2537.061f +
+ 7369.309f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const AdpcmDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 1827.665f +
+ 7913.808f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 2756.372f +
+ 9736.702f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const AdpcmDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1827.665f +
+ 7913.808f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1829.285f +
+ 9607.814f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1824.609f +
+ 6517.476f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2756.372f +
+ 9736.702f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2731.308f +
+ 12154.379f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2732.152f +
+ 7929.442f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const VolumeCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1311.1f);
+ case 240:
+ return static_cast<u32>(1713.6f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const VolumeRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1425.3f);
+ case 240:
+ return static_cast<u32>(1700.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const BiquadFilterCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(4173.2f);
+ case 240:
+ return static_cast<u32>(5585.1f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const MixCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1402.8f);
+ case 240:
+ return static_cast<u32>(1853.2f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const MixRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1968.7f);
+ case 240:
+ return static_cast<u32>(2459.4f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const MixRampGroupedCommand& command) const {
+ u32 count{0};
+ for (u32 i = 0; i < command.buffer_count; i++) {
+ if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
+ count++;
+ }
+ }
+
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) *
+ static_cast<f32>(count));
+ case 240:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) *
+ static_cast<f32>(count));
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const DepopPrepareCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(739.64f);
+ case 240:
+ return static_cast<u32>(910.97f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DelayCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(8929.042f);
+ case 2:
+ return static_cast<u32>(25500.75f);
+ case 4:
+ return static_cast<u32>(47759.617f);
+ case 6:
+ return static_cast<u32>(82203.07f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(1295.206f);
+ case 2:
+ return static_cast<u32>(1213.6f);
+ case 4:
+ return static_cast<u32>(942.028f);
+ case 6:
+ return static_cast<u32>(1001.553f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(11941.051f);
+ case 2:
+ return static_cast<u32>(37197.371f);
+ case 4:
+ return static_cast<u32>(69749.836f);
+ case 6:
+ return static_cast<u32>(120042.398f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(997.668f);
+ case 2:
+ return static_cast<u32>(977.634f);
+ case 4:
+ return static_cast<u32>(792.309f);
+ case 6:
+ return static_cast<u32>(875.427f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const UpsampleCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(312990.0f);
+ case 240:
+ return static_cast<u32>(0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9949.7f);
+ case 240:
+ return static_cast<u32>(14679.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const AuxCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ return static_cast<u32>(7182.136f);
+ }
+ return static_cast<u32>(472.111f);
+ case 240:
+ if (command.enabled) {
+ return static_cast<u32>(9435.961f);
+ }
+ return static_cast<u32>(462.619f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DeviceSinkCommand& command) const {
+ switch (command.input_count) {
+ case 2:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(8979.956f);
+ case 240:
+ return static_cast<u32>(9221.907f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 6:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9177.903f);
+ case 240:
+ return static_cast<u32>(9725.897f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const CircularBufferSinkCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(81475.055f);
+ case 2:
+ return static_cast<u32>(84975.0f);
+ case 4:
+ return static_cast<u32>(91625.148f);
+ case 6:
+ return static_cast<u32>(95332.266f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(536.298f);
+ case 2:
+ return static_cast<u32>(588.798f);
+ case 4:
+ return static_cast<u32>(643.702f);
+ case 6:
+ return static_cast<u32>(705.999f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(120174.469f);
+ case 2:
+ return static_cast<u32>(125262.219f);
+ case 4:
+ return static_cast<u32>(135751.234f);
+ case 6:
+ return static_cast<u32>(141129.234f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(617.641f);
+ case 2:
+ return static_cast<u32>(659.536f);
+ case 4:
+ return static_cast<u32>(711.438f);
+ case 6:
+ return static_cast<u32>(778.071f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const I3dl2ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(116754.984f);
+ case 2:
+ return static_cast<u32>(125912.055f);
+ case 4:
+ return static_cast<u32>(146336.031f);
+ case 6:
+ return static_cast<u32>(165812.656f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(735.0f);
+ case 2:
+ return static_cast<u32>(766.615f);
+ case 4:
+ return static_cast<u32>(834.067f);
+ case 6:
+ return static_cast<u32>(875.437f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(170292.344f);
+ case 2:
+ return static_cast<u32>(183875.625f);
+ case 4:
+ return static_cast<u32>(214696.188f);
+ case 6:
+ return static_cast<u32>(243846.766f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(508.473f);
+ case 2:
+ return static_cast<u32>(582.445f);
+ case 4:
+ return static_cast<u32>(626.419f);
+ case 6:
+ return static_cast<u32>(682.468f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const PerformanceCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(498.17f);
+ case 240:
+ return static_cast<u32>(489.42f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const ClearMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const CopyMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(842.59f);
+ case 240:
+ return static_cast<u32>(986.72f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const LightLimiterVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(21508.01f);
+ case 2:
+ return static_cast<u32>(23120.453f);
+ case 4:
+ return static_cast<u32>(26270.053f);
+ case 6:
+ return static_cast<u32>(40471.902f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(897.004f);
+ case 2:
+ return static_cast<u32>(931.549f);
+ case 4:
+ return static_cast<u32>(975.387f);
+ case 6:
+ return static_cast<u32>(1016.778f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30565.961f);
+ case 2:
+ return static_cast<u32>(32812.91f);
+ case 4:
+ return static_cast<u32>(37354.852f);
+ case 6:
+ return static_cast<u32>(58486.699f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(874.429f);
+ case 2:
+ return static_cast<u32>(921.553f);
+ case 4:
+ return static_cast<u32>(945.262f);
+ case 6:
+ return static_cast<u32>(992.26f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const LightLimiterVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(23639.584f);
+ case 2:
+ return static_cast<u32>(24666.725f);
+ case 4:
+ return static_cast<u32>(28876.459f);
+ case 6:
+ return static_cast<u32>(47096.078f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ } else {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(21508.01f);
+ case 2:
+ return static_cast<u32>(23120.453f);
+ case 4:
+ return static_cast<u32>(26270.053f);
+ case 6:
+ return static_cast<u32>(40471.902f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ }
+ } else if (command.parameter.processing_mode ==
+ LightLimiterInfo::ProcessingMode::Mode1) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(23639.584f);
+ case 2:
+ return static_cast<u32>(29954.062f);
+ case 4:
+ return static_cast<u32>(35807.477f);
+ case 6:
+ return static_cast<u32>(58339.773f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ } else {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(23639.584f);
+ case 2:
+ return static_cast<u32>(29954.062f);
+ case 4:
+ return static_cast<u32>(35807.477f);
+ case 6:
+ return static_cast<u32>(58339.773f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ }
+ } else {
+ LOG_ERROR(Service_Audio, "Invalid processing mode {}",
+ command.parameter.processing_mode);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(897.004f);
+ case 2:
+ return static_cast<u32>(931.549f);
+ case 4:
+ return static_cast<u32>(975.387f);
+ case 6:
+ return static_cast<u32>(1016.778f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(33875.023f);
+ case 2:
+ return static_cast<u32>(35199.938f);
+ case 4:
+ return static_cast<u32>(41371.230f);
+ case 6:
+ return static_cast<u32>(68370.914f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ } else {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30565.961f);
+ case 2:
+ return static_cast<u32>(32812.91f);
+ case 4:
+ return static_cast<u32>(37354.852f);
+ case 6:
+ return static_cast<u32>(58486.699f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ } else if (command.parameter.processing_mode ==
+ LightLimiterInfo::ProcessingMode::Mode1) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(33942.980f);
+ case 2:
+ return static_cast<u32>(28698.893f);
+ case 4:
+ return static_cast<u32>(34774.277f);
+ case 6:
+ return static_cast<u32>(61897.773f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ } else {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30610.248f);
+ case 2:
+ return static_cast<u32>(26322.408f);
+ case 4:
+ return static_cast<u32>(30369.000f);
+ case 6:
+ return static_cast<u32>(51892.090f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ } else {
+ LOG_ERROR(Service_Audio, "Invalid processing mode {}",
+ command.parameter.processing_mode);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(874.429f);
+ case 2:
+ return static_cast<u32>(921.553f);
+ case 4:
+ return static_cast<u32>(945.262f);
+ case 6:
+ return static_cast<u32>(992.26f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(7424.5f);
+ case 240:
+ return static_cast<u32>(9730.4f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CaptureCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ return static_cast<u32>(426.982f);
+ }
+ return static_cast<u32>(4261.005f);
+ case 240:
+ if (command.enabled) {
+ return static_cast<u32>(435.204f);
+ }
+ return static_cast<u32>(5858.265f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CompressorCommand& command) const {
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(34430.570f);
+ case 240:
+ return static_cast<u32>(51095.348f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 2:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(44253.320f);
+ case 240:
+ return static_cast<u32>(65693.094f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 4:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(63827.457f);
+ case 240:
+ return static_cast<u32>(95382.852f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 6:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(83361.484f);
+ case 240:
+ return static_cast<u32>(124509.906f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(630.115f);
+ case 240:
+ return static_cast<u32>(840.136f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 2:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(638.274f);
+ case 240:
+ return static_cast<u32>(826.098f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 4:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(705.862f);
+ case 240:
+ return static_cast<u32>(901.876f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 6:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(782.019f);
+ case 240:
+ return static_cast<u32>(965.286f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.h b/src/audio_core/renderer/command/command_processing_time_estimator.h
new file mode 100644
index 000000000..452217196
--- /dev/null
+++ b/src/audio_core/renderer/command/command_processing_time_estimator.h
@@ -0,0 +1,254 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/command/commands.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Estimate the processing time required for all commands.
+ */
+class ICommandProcessingTimeEstimator {
+public:
+ virtual ~ICommandProcessingTimeEstimator() = default;
+
+ virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0;
+ virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0;
+ virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0;
+ virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0;
+ virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0;
+ virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0;
+ virtual u32 Estimate(const VolumeCommand& command) const = 0;
+ virtual u32 Estimate(const VolumeRampCommand& command) const = 0;
+ virtual u32 Estimate(const BiquadFilterCommand& command) const = 0;
+ virtual u32 Estimate(const MixCommand& command) const = 0;
+ virtual u32 Estimate(const MixRampCommand& command) const = 0;
+ virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0;
+ virtual u32 Estimate(const DepopPrepareCommand& command) const = 0;
+ virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0;
+ virtual u32 Estimate(const DelayCommand& command) const = 0;
+ virtual u32 Estimate(const UpsampleCommand& command) const = 0;
+ virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0;
+ virtual u32 Estimate(const AuxCommand& command) const = 0;
+ virtual u32 Estimate(const DeviceSinkCommand& command) const = 0;
+ virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0;
+ virtual u32 Estimate(const ReverbCommand& command) const = 0;
+ virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0;
+ virtual u32 Estimate(const PerformanceCommand& command) const = 0;
+ virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0;
+ virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0;
+ virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0;
+ virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0;
+ virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0;
+ virtual u32 Estimate(const CaptureCommand& command) const = 0;
+ virtual u32 Estimate(const CompressorCommand& command) const = 0;
+};
+
+class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator {
+public:
+ CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_)
+ : sample_count{sample_count_}, buffer_count{buffer_count_} {}
+
+ u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
+ u32 Estimate(const VolumeCommand& command) const override;
+ u32 Estimate(const VolumeRampCommand& command) const override;
+ u32 Estimate(const BiquadFilterCommand& command) const override;
+ u32 Estimate(const MixCommand& command) const override;
+ u32 Estimate(const MixRampCommand& command) const override;
+ u32 Estimate(const MixRampGroupedCommand& command) const override;
+ u32 Estimate(const DepopPrepareCommand& command) const override;
+ u32 Estimate(const DepopForMixBuffersCommand& command) const override;
+ u32 Estimate(const DelayCommand& command) const override;
+ u32 Estimate(const UpsampleCommand& command) const override;
+ u32 Estimate(const DownMix6chTo2chCommand& command) const override;
+ u32 Estimate(const AuxCommand& command) const override;
+ u32 Estimate(const DeviceSinkCommand& command) const override;
+ u32 Estimate(const CircularBufferSinkCommand& command) const override;
+ u32 Estimate(const ReverbCommand& command) const override;
+ u32 Estimate(const I3dl2ReverbCommand& command) const override;
+ u32 Estimate(const PerformanceCommand& command) const override;
+ u32 Estimate(const ClearMixBufferCommand& command) const override;
+ u32 Estimate(const CopyMixBufferCommand& command) const override;
+ u32 Estimate(const LightLimiterVersion1Command& command) const override;
+ u32 Estimate(const LightLimiterVersion2Command& command) const override;
+ u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
+ u32 Estimate(const CaptureCommand& command) const override;
+ u32 Estimate(const CompressorCommand& command) const override;
+
+private:
+ u32 sample_count{};
+ u32 buffer_count{};
+};
+
+class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator {
+public:
+ CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_)
+ : sample_count{sample_count_}, buffer_count{buffer_count_} {}
+
+ u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
+ u32 Estimate(const VolumeCommand& command) const override;
+ u32 Estimate(const VolumeRampCommand& command) const override;
+ u32 Estimate(const BiquadFilterCommand& command) const override;
+ u32 Estimate(const MixCommand& command) const override;
+ u32 Estimate(const MixRampCommand& command) const override;
+ u32 Estimate(const MixRampGroupedCommand& command) const override;
+ u32 Estimate(const DepopPrepareCommand& command) const override;
+ u32 Estimate(const DepopForMixBuffersCommand& command) const override;
+ u32 Estimate(const DelayCommand& command) const override;
+ u32 Estimate(const UpsampleCommand& command) const override;
+ u32 Estimate(const DownMix6chTo2chCommand& command) const override;
+ u32 Estimate(const AuxCommand& command) const override;
+ u32 Estimate(const DeviceSinkCommand& command) const override;
+ u32 Estimate(const CircularBufferSinkCommand& command) const override;
+ u32 Estimate(const ReverbCommand& command) const override;
+ u32 Estimate(const I3dl2ReverbCommand& command) const override;
+ u32 Estimate(const PerformanceCommand& command) const override;
+ u32 Estimate(const ClearMixBufferCommand& command) const override;
+ u32 Estimate(const CopyMixBufferCommand& command) const override;
+ u32 Estimate(const LightLimiterVersion1Command& command) const override;
+ u32 Estimate(const LightLimiterVersion2Command& command) const override;
+ u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
+ u32 Estimate(const CaptureCommand& command) const override;
+ u32 Estimate(const CompressorCommand& command) const override;
+
+private:
+ u32 sample_count{};
+ u32 buffer_count{};
+};
+
+class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator {
+public:
+ CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_)
+ : sample_count{sample_count_}, buffer_count{buffer_count_} {}
+
+ u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
+ u32 Estimate(const VolumeCommand& command) const override;
+ u32 Estimate(const VolumeRampCommand& command) const override;
+ u32 Estimate(const BiquadFilterCommand& command) const override;
+ u32 Estimate(const MixCommand& command) const override;
+ u32 Estimate(const MixRampCommand& command) const override;
+ u32 Estimate(const MixRampGroupedCommand& command) const override;
+ u32 Estimate(const DepopPrepareCommand& command) const override;
+ u32 Estimate(const DepopForMixBuffersCommand& command) const override;
+ u32 Estimate(const DelayCommand& command) const override;
+ u32 Estimate(const UpsampleCommand& command) const override;
+ u32 Estimate(const DownMix6chTo2chCommand& command) const override;
+ u32 Estimate(const AuxCommand& command) const override;
+ u32 Estimate(const DeviceSinkCommand& command) const override;
+ u32 Estimate(const CircularBufferSinkCommand& command) const override;
+ u32 Estimate(const ReverbCommand& command) const override;
+ u32 Estimate(const I3dl2ReverbCommand& command) const override;
+ u32 Estimate(const PerformanceCommand& command) const override;
+ u32 Estimate(const ClearMixBufferCommand& command) const override;
+ u32 Estimate(const CopyMixBufferCommand& command) const override;
+ u32 Estimate(const LightLimiterVersion1Command& command) const override;
+ u32 Estimate(const LightLimiterVersion2Command& command) const override;
+ u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
+ u32 Estimate(const CaptureCommand& command) const override;
+ u32 Estimate(const CompressorCommand& command) const override;
+
+private:
+ u32 sample_count{};
+ u32 buffer_count{};
+};
+
+class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator {
+public:
+ CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_)
+ : sample_count{sample_count_}, buffer_count{buffer_count_} {}
+
+ u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
+ u32 Estimate(const VolumeCommand& command) const override;
+ u32 Estimate(const VolumeRampCommand& command) const override;
+ u32 Estimate(const BiquadFilterCommand& command) const override;
+ u32 Estimate(const MixCommand& command) const override;
+ u32 Estimate(const MixRampCommand& command) const override;
+ u32 Estimate(const MixRampGroupedCommand& command) const override;
+ u32 Estimate(const DepopPrepareCommand& command) const override;
+ u32 Estimate(const DepopForMixBuffersCommand& command) const override;
+ u32 Estimate(const DelayCommand& command) const override;
+ u32 Estimate(const UpsampleCommand& command) const override;
+ u32 Estimate(const DownMix6chTo2chCommand& command) const override;
+ u32 Estimate(const AuxCommand& command) const override;
+ u32 Estimate(const DeviceSinkCommand& command) const override;
+ u32 Estimate(const CircularBufferSinkCommand& command) const override;
+ u32 Estimate(const ReverbCommand& command) const override;
+ u32 Estimate(const I3dl2ReverbCommand& command) const override;
+ u32 Estimate(const PerformanceCommand& command) const override;
+ u32 Estimate(const ClearMixBufferCommand& command) const override;
+ u32 Estimate(const CopyMixBufferCommand& command) const override;
+ u32 Estimate(const LightLimiterVersion1Command& command) const override;
+ u32 Estimate(const LightLimiterVersion2Command& command) const override;
+ u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
+ u32 Estimate(const CaptureCommand& command) const override;
+ u32 Estimate(const CompressorCommand& command) const override;
+
+private:
+ u32 sample_count{};
+ u32 buffer_count{};
+};
+
+class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator {
+public:
+ CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_)
+ : sample_count{sample_count_}, buffer_count{buffer_count_} {}
+
+ u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
+ u32 Estimate(const VolumeCommand& command) const override;
+ u32 Estimate(const VolumeRampCommand& command) const override;
+ u32 Estimate(const BiquadFilterCommand& command) const override;
+ u32 Estimate(const MixCommand& command) const override;
+ u32 Estimate(const MixRampCommand& command) const override;
+ u32 Estimate(const MixRampGroupedCommand& command) const override;
+ u32 Estimate(const DepopPrepareCommand& command) const override;
+ u32 Estimate(const DepopForMixBuffersCommand& command) const override;
+ u32 Estimate(const DelayCommand& command) const override;
+ u32 Estimate(const UpsampleCommand& command) const override;
+ u32 Estimate(const DownMix6chTo2chCommand& command) const override;
+ u32 Estimate(const AuxCommand& command) const override;
+ u32 Estimate(const DeviceSinkCommand& command) const override;
+ u32 Estimate(const CircularBufferSinkCommand& command) const override;
+ u32 Estimate(const ReverbCommand& command) const override;
+ u32 Estimate(const I3dl2ReverbCommand& command) const override;
+ u32 Estimate(const PerformanceCommand& command) const override;
+ u32 Estimate(const ClearMixBufferCommand& command) const override;
+ u32 Estimate(const CopyMixBufferCommand& command) const override;
+ u32 Estimate(const LightLimiterVersion1Command& command) const override;
+ u32 Estimate(const LightLimiterVersion2Command& command) const override;
+ u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
+ u32 Estimate(const CaptureCommand& command) const override;
+ u32 Estimate(const CompressorCommand& command) const override;
+
+private:
+ u32 sample_count{};
+ u32 buffer_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/commands.h b/src/audio_core/renderer/command/commands.h
new file mode 100644
index 000000000..6d8b8546d
--- /dev/null
+++ b/src/audio_core/renderer/command/commands.h
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/command/data_source/adpcm.h"
+#include "audio_core/renderer/command/data_source/pcm_float.h"
+#include "audio_core/renderer/command/data_source/pcm_int16.h"
+#include "audio_core/renderer/command/effect/aux_.h"
+#include "audio_core/renderer/command/effect/biquad_filter.h"
+#include "audio_core/renderer/command/effect/capture.h"
+#include "audio_core/renderer/command/effect/compressor.h"
+#include "audio_core/renderer/command/effect/delay.h"
+#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
+#include "audio_core/renderer/command/effect/light_limiter.h"
+#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
+#include "audio_core/renderer/command/effect/reverb.h"
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/command/mix/clear_mix.h"
+#include "audio_core/renderer/command/mix/copy_mix.h"
+#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
+#include "audio_core/renderer/command/mix/depop_prepare.h"
+#include "audio_core/renderer/command/mix/mix.h"
+#include "audio_core/renderer/command/mix/mix_ramp.h"
+#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
+#include "audio_core/renderer/command/mix/volume.h"
+#include "audio_core/renderer/command/mix/volume_ramp.h"
+#include "audio_core/renderer/command/performance/performance.h"
+#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
+#include "audio_core/renderer/command/resample/upsample.h"
+#include "audio_core/renderer/command/sink/circular_buffer.h"
+#include "audio_core/renderer/command/sink/device.h"
diff --git a/src/audio_core/renderer/command/data_source/adpcm.cpp b/src/audio_core/renderer/command/data_source/adpcm.cpp
new file mode 100644
index 000000000..e66ed2990
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/adpcm.cpp
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <span>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/data_source/adpcm.h"
+#include "audio_core/renderer/command/data_source/decode.h"
+
+namespace AudioCore::AudioRenderer {
+
+void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample "
+ "rate {} target sample rate {} src quality {}\n",
+ output_index, sample_rate, processor.target_sample_rate, src_quality);
+}
+
+void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
+ auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+
+ DecodeFromWaveBuffersArgs args{
+ .sample_format{SampleFormat::Adpcm},
+ .output{out_buffer},
+ .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
+ .wave_buffers{wave_buffers},
+ .channel{0},
+ .channel_count{1},
+ .src_quality{src_quality},
+ .pitch{pitch},
+ .source_sample_rate{sample_rate},
+ .target_sample_rate{processor.target_sample_rate},
+ .sample_count{processor.sample_count},
+ .data_address{data_address},
+ .data_size{data_size},
+ .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
+ .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
+ };
+
+ DecodeFromWaveBuffers(*processor.memory, args);
+}
+
+bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample "
+ "rate {} target sample rate {} src quality {}\n",
+ output_index, sample_rate, processor.target_sample_rate, src_quality);
+}
+
+void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
+ auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+
+ DecodeFromWaveBuffersArgs args{
+ .sample_format{SampleFormat::Adpcm},
+ .output{out_buffer},
+ .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
+ .wave_buffers{wave_buffers},
+ .channel{0},
+ .channel_count{1},
+ .src_quality{src_quality},
+ .pitch{pitch},
+ .source_sample_rate{sample_rate},
+ .target_sample_rate{processor.target_sample_rate},
+ .sample_count{processor.sample_count},
+ .data_address{data_address},
+ .data_size{data_size},
+ .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
+ .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
+ };
+
+ DecodeFromWaveBuffers(*processor.memory, args);
+}
+
+bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/adpcm.h b/src/audio_core/renderer/command/data_source/adpcm.h
new file mode 100644
index 000000000..a9cf9cee4
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/adpcm.h
@@ -0,0 +1,119 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/common/common.h"
+#include "audio_core/common/wave_buffer.h"
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers
+ * into the output_index mix buffer.
+ */
+struct AdpcmDataSourceVersion1Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Quality used for sample rate conversion
+ SrcQuality src_quality;
+ /// Mix buffer index for decoded samples
+ s16 output_index;
+ /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ u16 flags;
+ /// Wavebuffer sample rate
+ u32 sample_rate;
+ /// Pitch used for sample rate conversion
+ f32 pitch;
+ /// Wavebuffers containing the wavebuffer address, context address, looping information etc
+ std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
+ /// Voice state, updated each call and written back to game
+ CpuAddr voice_state;
+ /// Coefficients data address
+ CpuAddr data_address;
+ /// Coefficients data size
+ u64 data_size;
+};
+
+/**
+ * AudioRenderer command to decode ADPCM-encoded version 2 wavebuffers
+ * into the output_index mix buffer.
+ */
+struct AdpcmDataSourceVersion2Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Quality used for sample rate conversion
+ SrcQuality src_quality;
+ /// Mix buffer index for decoded samples
+ s16 output_index;
+ /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ u16 flags;
+ /// Wavebuffer sample rate
+ u32 sample_rate;
+ /// Pitch used for sample rate conversion
+ f32 pitch;
+ /// Target channel to read within the wavebuffer
+ s8 channel_index;
+ /// Number of channels within the wavebuffer
+ s8 channel_count;
+ /// Wavebuffers containing the wavebuffer address, context address, looping information etc
+ std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
+ /// Voice state, updated each call and written back to game
+ CpuAddr voice_state;
+ /// Coefficients data address
+ CpuAddr data_address;
+ /// Coefficients data size
+ u64 data_size;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/decode.cpp b/src/audio_core/renderer/command/data_source/decode.cpp
new file mode 100644
index 000000000..ff5d31bd6
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/decode.cpp
@@ -0,0 +1,428 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <vector>
+
+#include "audio_core/renderer/command/data_source/decode.h"
+#include "audio_core/renderer/command/resample/resample.h"
+#include "common/fixed_point.h"
+#include "common/logging/log.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer {
+
+constexpr u32 TempBufferSize = 0x3F00;
+constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
+
+/**
+ * Decode PCM data. Only s16 or f32 is supported.
+ *
+ * @tparam T - Type to decode. Only s16 and f32 are supported.
+ * @param memory - Core memory for reading samples.
+ * @param out_buffer - Output mix buffer to receive the samples.
+ * @param req - Information for how to decode.
+ * @return Number of samples decoded.
+ */
+template <typename T>
+static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
+ const DecodeArg& req) {
+ constexpr s32 min{std::numeric_limits<s16>::min()};
+ constexpr s32 max{std::numeric_limits<s16>::max()};
+
+ if (req.buffer == 0 || req.buffer_size == 0) {
+ return 0;
+ }
+
+ if (req.start_offset >= req.end_offset) {
+ return 0;
+ }
+
+ auto samples_to_decode{
+ std::min(req.samples_to_read, req.end_offset - req.start_offset - req.offset)};
+ u32 channel_count{static_cast<u32>(req.channel_count)};
+
+ switch (req.channel_count) {
+ default: {
+ const VAddr source{req.buffer +
+ (((req.start_offset + req.offset) * channel_count) * sizeof(T))};
+ const u64 size{channel_count * samples_to_decode};
+ const u64 size_bytes{size * sizeof(T)};
+
+ std::vector<T> samples(size);
+ memory.ReadBlockUnsafe(source, samples.data(), size_bytes);
+
+ if constexpr (std::is_floating_point_v<T>) {
+ for (u32 i = 0; i < samples_to_decode; i++) {
+ auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
+ std::numeric_limits<s16>::max())};
+ out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
+ }
+ } else {
+ for (u32 i = 0; i < samples_to_decode; i++) {
+ out_buffer[i] = samples[i * channel_count + req.target_channel];
+ }
+ }
+ } break;
+
+ case 1:
+ if (req.target_channel != 0) {
+ LOG_ERROR(Service_Audio, "Invalid target channel, expected 0, got {}",
+ req.target_channel);
+ return 0;
+ }
+
+ const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))};
+ std::vector<T> samples(samples_to_decode);
+ memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T));
+
+ if constexpr (std::is_floating_point_v<T>) {
+ for (u32 i = 0; i < samples_to_decode; i++) {
+ auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
+ std::numeric_limits<s16>::max())};
+ out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
+ }
+ } else {
+ std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16));
+ }
+ break;
+ }
+
+ return samples_to_decode;
+}
+
+/**
+ * Decode ADPCM data.
+ *
+ * @param memory - Core memory for reading samples.
+ * @param out_buffer - Output mix buffer to receive the samples.
+ * @param req - Information for how to decode.
+ * @return Number of samples decoded.
+ */
+static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
+ const DecodeArg& req) {
+ constexpr u32 SamplesPerFrame{14};
+ constexpr u32 NibblesPerFrame{16};
+
+ if (req.buffer == 0 || req.buffer_size == 0) {
+ return 0;
+ }
+
+ if (req.end_offset < req.start_offset) {
+ return 0;
+ }
+
+ auto end{(req.end_offset % SamplesPerFrame) +
+ NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
+ if (req.end_offset % SamplesPerFrame) {
+ end += 3;
+ } else {
+ end += 1;
+ }
+
+ if (req.buffer_size < end / 2) {
+ return 0;
+ }
+
+ auto samples_to_process{
+ std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
+
+ auto samples_to_read{samples_to_process};
+ auto start_pos{req.start_offset + req.offset};
+ auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
+ auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
+ samples_remaining_in_frame};
+
+ if (samples_remaining_in_frame) {
+ position_in_frame += 2;
+ }
+
+ const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)};
+ std::vector<u8> wavebuffer(size);
+ memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(),
+ wavebuffer.size());
+
+ auto context{req.adpcm_context};
+ auto header{context->header};
+ u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)};
+ u8 scale{static_cast<u8>(header & 0xFU)};
+ s32 coeff0{req.coefficients[coeff_index * 2 + 0]};
+ s32 coeff1{req.coefficients[coeff_index * 2 + 1]};
+
+ auto yn0{context->yn0};
+ auto yn1{context->yn1};
+
+ static constexpr std::array<s32, 16> Steps{
+ 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
+ };
+
+ const auto decode_sample = [&](const s32 code) -> s16 {
+ const auto xn = code * (1 << scale);
+ const auto prediction = coeff0 * yn0 + coeff1 * yn1;
+ const auto sample = ((xn << 11) + 0x400 + prediction) >> 11;
+ const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF);
+ yn1 = yn0;
+ yn0 = static_cast<s16>(saturated);
+ return yn0;
+ };
+
+ u32 read_index{0};
+ u32 write_index{0};
+
+ while (samples_to_read > 0) {
+ // Are we at a new frame?
+ if ((position_in_frame % NibblesPerFrame) == 0) {
+ header = wavebuffer[read_index++];
+ coeff_index = (header >> 4) & 0xF;
+ scale = header & 0xF;
+ coeff0 = req.coefficients[coeff_index * 2 + 0];
+ coeff1 = req.coefficients[coeff_index * 2 + 1];
+ position_in_frame += 2;
+
+ // Can we consume all of this frame's samples?
+ if (samples_to_read >= SamplesPerFrame) {
+ // Can grab all samples until the next header
+ for (u32 i = 0; i < SamplesPerFrame / 2; i++) {
+ auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]};
+ auto code1{Steps[wavebuffer[read_index] & 0xF]};
+ read_index++;
+
+ out_buffer[write_index++] = decode_sample(code0);
+ out_buffer[write_index++] = decode_sample(code1);
+ }
+
+ position_in_frame += SamplesPerFrame;
+ samples_to_read -= SamplesPerFrame;
+ continue;
+ }
+ }
+
+ // Decode a single sample
+ auto code{wavebuffer[read_index]};
+ if (position_in_frame & 1) {
+ code &= 0xF;
+ read_index++;
+ } else {
+ code >>= 4;
+ }
+
+ out_buffer[write_index++] = decode_sample(Steps[code]);
+
+ position_in_frame++;
+ samples_to_read--;
+ }
+
+ context->header = header;
+ context->yn0 = yn0;
+ context->yn1 = yn1;
+
+ return samples_to_process;
+}
+
+/**
+ * Decode implementation.
+ * Decode wavebuffers according to the given args.
+ *
+ * @param memory - Core memory to read data from.
+ * @param args - The wavebuffer data, and information for how to decode it.
+ */
+void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
+ auto& voice_state{*args.voice_state};
+ auto remaining_sample_count{args.sample_count};
+ auto fraction{voice_state.fraction};
+
+ const auto sample_rate_ratio{
+ (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
+ args.pitch};
+ const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
+
+ if (size_required < 0) {
+ return;
+ }
+
+ auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]};
+ if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) {
+ return;
+ }
+
+ auto max_remaining_sample_count{
+ ((Common::FixedPoint<17, 15>(TempBufferSize) - fraction) / sample_rate_ratio)
+ .to_uint_floor()};
+ max_remaining_sample_count = std::min(max_remaining_sample_count, remaining_sample_count);
+
+ auto wavebuffers_consumed{voice_state.wave_buffers_consumed};
+ auto wavebuffer_index{voice_state.wave_buffer_index};
+ auto played_sample_count{voice_state.played_sample_count};
+
+ bool is_buffer_starved{false};
+ u32 offset{voice_state.offset};
+
+ auto output_buffer{args.output};
+ std::vector<s16> temp_buffer(TempBufferSize, 0);
+
+ while (remaining_sample_count > 0) {
+ const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)};
+ const auto samples_to_read{
+ (fraction + samples_to_write * sample_rate_ratio).to_uint_floor()};
+
+ u32 temp_buffer_pos{0};
+
+ if (!args.IsVoicePitchAndSrcSkippedSupported) {
+ for (u32 i = 0; i < pitch; i++) {
+ temp_buffer[i] = voice_state.sample_history[i];
+ }
+ temp_buffer_pos = pitch;
+ }
+
+ u32 samples_read{0};
+ while (samples_read < samples_to_read) {
+ if (wavebuffer_index >= MaxWaveBuffers) {
+ LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index);
+ wavebuffer_index = 0;
+ voice_state.wave_buffer_valid.fill(false);
+ wavebuffers_consumed = MaxWaveBuffers;
+ }
+
+ if (!voice_state.wave_buffer_valid[wavebuffer_index]) {
+ is_buffer_starved = true;
+ break;
+ }
+
+ auto& wavebuffer{args.wave_buffers[wavebuffer_index]};
+
+ if (offset == 0 && args.sample_format == SampleFormat::Adpcm &&
+ wavebuffer.context != 0) {
+ memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context,
+ wavebuffer.context_size);
+ }
+
+ auto start_offset{wavebuffer.start_offset};
+ auto end_offset{wavebuffer.end_offset};
+
+ if (wavebuffer.loop && voice_state.loop_count > 0 &&
+ wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
+ wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
+ start_offset = wavebuffer.loop_start_offset;
+ end_offset = wavebuffer.loop_end_offset;
+ }
+
+ DecodeArg decode_arg{.buffer{wavebuffer.buffer},
+ .buffer_size{wavebuffer.buffer_size},
+ .start_offset{start_offset},
+ .end_offset{end_offset},
+ .channel_count{args.channel_count},
+ .coefficients{},
+ .adpcm_context{nullptr},
+ .target_channel{args.channel},
+ .offset{offset},
+ .samples_to_read{samples_to_read - samples_read}};
+
+ s32 samples_decoded{0};
+
+ switch (args.sample_format) {
+ case SampleFormat::PcmInt16:
+ samples_decoded = DecodePcm<s16>(
+ memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
+ decode_arg);
+ break;
+
+ case SampleFormat::PcmFloat:
+ samples_decoded = DecodePcm<f32>(
+ memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
+ decode_arg);
+ break;
+
+ case SampleFormat::Adpcm: {
+ decode_arg.adpcm_context = &voice_state.adpcm_context;
+ memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size);
+ samples_decoded = DecodeAdpcm(
+ memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
+ decode_arg);
+ } break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample format to decode {}",
+ static_cast<u32>(args.sample_format));
+ samples_decoded = 0;
+ break;
+ }
+
+ played_sample_count += samples_decoded;
+ samples_read += samples_decoded;
+ temp_buffer_pos += samples_decoded;
+ offset += samples_decoded;
+
+ if (samples_decoded == 0 || offset >= end_offset - start_offset) {
+ offset = 0;
+ if (!wavebuffer.loop) {
+ voice_state.wave_buffer_valid[wavebuffer_index] = false;
+ voice_state.loop_count = 0;
+
+ if (wavebuffer.stream_ended) {
+ played_sample_count = 0;
+ }
+
+ wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
+ wavebuffers_consumed++;
+ } else {
+ voice_state.loop_count++;
+ if (wavebuffer.loop_count > 0 &&
+ (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
+ voice_state.wave_buffer_valid[wavebuffer_index] = false;
+ voice_state.loop_count = 0;
+
+ if (wavebuffer.stream_ended) {
+ played_sample_count = 0;
+ }
+
+ wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
+ wavebuffers_consumed++;
+ }
+
+ if (samples_decoded == 0) {
+ is_buffer_starved = true;
+ break;
+ }
+
+ if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
+ played_sample_count = 0;
+ }
+ }
+ }
+ }
+
+ if (args.IsVoicePitchAndSrcSkippedSupported) {
+ if (samples_read > output_buffer.size()) {
+ LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!");
+ }
+ for (u32 i = 0; i < samples_read; i++) {
+ output_buffer[i] = temp_buffer[i];
+ }
+ } else {
+ std::memset(&temp_buffer[temp_buffer_pos], 0,
+ (samples_to_read - samples_read) * sizeof(s16));
+
+ Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write,
+ args.src_quality);
+
+ std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read],
+ pitch * sizeof(s16));
+ }
+
+ remaining_sample_count -= samples_to_write;
+ if (remaining_sample_count != 0 && is_buffer_starved) {
+ LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??");
+ break;
+ }
+
+ output_buffer = output_buffer.subspan(samples_to_write);
+ }
+
+ voice_state.wave_buffers_consumed = wavebuffers_consumed;
+ voice_state.played_sample_count = played_sample_count;
+ voice_state.wave_buffer_index = wavebuffer_index;
+ voice_state.offset = offset;
+ voice_state.fraction = fraction;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/decode.h b/src/audio_core/renderer/command/data_source/decode.h
new file mode 100644
index 000000000..4d63d6fa8
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/decode.h
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "audio_core/common/wave_buffer.h"
+#include "audio_core/renderer/voice/voice_state.h"
+#include "common/common_types.h"
+
+namespace Core::Memory {
+class Memory;
+}
+
+namespace AudioCore::AudioRenderer {
+
+struct DecodeFromWaveBuffersArgs {
+ SampleFormat sample_format;
+ std::span<s32> output;
+ VoiceState* voice_state;
+ std::span<WaveBufferVersion2> wave_buffers;
+ s8 channel;
+ s8 channel_count;
+ SrcQuality src_quality;
+ f32 pitch;
+ u32 source_sample_rate;
+ u32 target_sample_rate;
+ u32 sample_count;
+ CpuAddr data_address;
+ u64 data_size;
+ bool IsVoicePlayedSampleCountResetAtLoopPointSupported;
+ bool IsVoicePitchAndSrcSkippedSupported;
+};
+
+struct DecodeArg {
+ CpuAddr buffer;
+ u64 buffer_size;
+ u32 start_offset;
+ u32 end_offset;
+ s8 channel_count;
+ std::array<s16, 16> coefficients;
+ VoiceState::AdpcmContext* adpcm_context;
+ s8 target_channel;
+ u32 offset;
+ u32 samples_to_read;
+};
+
+/**
+ * Decode wavebuffers according to the given args.
+ *
+ * @param memory - Core memory to read data from.
+ * @param args - The wavebuffer data, and information for how to decode it.
+ */
+void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args);
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_float.cpp b/src/audio_core/renderer/command/data_source/pcm_float.cpp
new file mode 100644
index 000000000..be77fab69
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/pcm_float.cpp
@@ -0,0 +1,86 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/data_source/decode.h"
+#include "audio_core/renderer/command/data_source/pcm_float.h"
+
+namespace AudioCore::AudioRenderer {
+
+void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string +=
+ fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} "
+ "channel count {} source sample rate {} target sample rate {} src quality {}\n",
+ output_index, channel_index, channel_count, sample_rate,
+ processor.target_sample_rate, src_quality);
+}
+
+void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
+ auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count);
+
+ DecodeFromWaveBuffersArgs args{
+ .sample_format{SampleFormat::PcmFloat},
+ .output{out_buffer},
+ .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
+ .wave_buffers{wave_buffers},
+ .channel{channel_index},
+ .channel_count{channel_count},
+ .src_quality{src_quality},
+ .pitch{pitch},
+ .source_sample_rate{sample_rate},
+ .target_sample_rate{processor.target_sample_rate},
+ .sample_count{processor.sample_count},
+ .data_address{0},
+ .data_size{0},
+ .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
+ .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
+ };
+
+ DecodeFromWaveBuffers(*processor.memory, args);
+}
+
+bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string +=
+ fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} "
+ "channel count {} source sample rate {} target sample rate {} src quality {}\n",
+ output_index, channel_index, channel_count, sample_rate,
+ processor.target_sample_rate, src_quality);
+}
+
+void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
+ auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count);
+
+ DecodeFromWaveBuffersArgs args{
+ .sample_format{SampleFormat::PcmFloat},
+ .output{out_buffer},
+ .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
+ .wave_buffers{wave_buffers},
+ .channel{channel_index},
+ .channel_count{channel_count},
+ .src_quality{src_quality},
+ .pitch{pitch},
+ .source_sample_rate{sample_rate},
+ .target_sample_rate{processor.target_sample_rate},
+ .sample_count{processor.sample_count},
+ .data_address{0},
+ .data_size{0},
+ .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
+ .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
+ };
+
+ DecodeFromWaveBuffers(*processor.memory, args);
+}
+
+bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_float.h b/src/audio_core/renderer/command/data_source/pcm_float.h
new file mode 100644
index 000000000..e4af77c20
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/pcm_float.h
@@ -0,0 +1,113 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/common/wave_buffer.h"
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command to decode PCM float-encoded version 1 wavebuffers
+ * into the output_index mix buffer.
+ */
+struct PcmFloatDataSourceVersion1Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Quality used for sample rate conversion
+ SrcQuality src_quality;
+ /// Mix buffer index for decoded samples
+ s16 output_index;
+ /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ u16 flags;
+ /// Wavebuffer sample rate
+ u32 sample_rate;
+ /// Pitch used for sample rate conversion
+ f32 pitch;
+ /// Target channel to read within the wavebuffer
+ s8 channel_index;
+ /// Number of channels within the wavebuffer
+ s8 channel_count;
+ /// Wavebuffers containing the wavebuffer address, context address, looping information etc
+ std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
+ /// Voice state, updated each call and written back to game
+ CpuAddr voice_state;
+};
+
+/**
+ * AudioRenderer command to decode PCM float-encoded version 2 wavebuffers
+ * into the output_index mix buffer.
+ */
+struct PcmFloatDataSourceVersion2Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Quality used for sample rate conversion
+ SrcQuality src_quality;
+ /// Mix buffer index for decoded samples
+ s16 output_index;
+ /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ u16 flags;
+ /// Wavebuffer sample rate
+ u32 sample_rate;
+ /// Pitch used for sample rate conversion
+ f32 pitch;
+ /// Target channel to read within the wavebuffer
+ s8 channel_index;
+ /// Number of channels within the wavebuffer
+ s8 channel_count;
+ /// Wavebuffers containing the wavebuffer address, context address, looping information etc
+ std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
+ /// Voice state, updated each call and written back to game
+ CpuAddr voice_state;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.cpp b/src/audio_core/renderer/command/data_source/pcm_int16.cpp
new file mode 100644
index 000000000..7a27463e4
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/pcm_int16.cpp
@@ -0,0 +1,87 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <span>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/data_source/decode.h"
+#include "audio_core/renderer/command/data_source/pcm_int16.h"
+
+namespace AudioCore::AudioRenderer {
+
+void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string +=
+ fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} "
+ "channel count {} source sample rate {} target sample rate {} src quality {}\n",
+ output_index, channel_index, channel_count, sample_rate,
+ processor.target_sample_rate, src_quality);
+}
+
+void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
+ auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count);
+
+ DecodeFromWaveBuffersArgs args{
+ .sample_format{SampleFormat::PcmInt16},
+ .output{out_buffer},
+ .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
+ .wave_buffers{wave_buffers},
+ .channel{channel_index},
+ .channel_count{channel_count},
+ .src_quality{src_quality},
+ .pitch{pitch},
+ .source_sample_rate{sample_rate},
+ .target_sample_rate{processor.target_sample_rate},
+ .sample_count{processor.sample_count},
+ .data_address{0},
+ .data_size{0},
+ .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
+ .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
+ };
+
+ DecodeFromWaveBuffers(*processor.memory, args);
+}
+
+bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string +=
+ fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} "
+ "channel count {} source sample rate {} target sample rate {} src quality {}\n",
+ output_index, channel_index, channel_count, sample_rate,
+ processor.target_sample_rate, src_quality);
+}
+
+void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
+ auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count);
+ DecodeFromWaveBuffersArgs args{
+ .sample_format{SampleFormat::PcmInt16},
+ .output{out_buffer},
+ .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
+ .wave_buffers{wave_buffers},
+ .channel{channel_index},
+ .channel_count{channel_count},
+ .src_quality{src_quality},
+ .pitch{pitch},
+ .source_sample_rate{sample_rate},
+ .target_sample_rate{processor.target_sample_rate},
+ .sample_count{processor.sample_count},
+ .data_address{0},
+ .data_size{0},
+ .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
+ .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
+ };
+
+ DecodeFromWaveBuffers(*processor.memory, args);
+}
+
+bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.h b/src/audio_core/renderer/command/data_source/pcm_int16.h
new file mode 100644
index 000000000..5de1ad60d
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/pcm_int16.h
@@ -0,0 +1,110 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/common/wave_buffer.h"
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers
+ * into the output_index mix buffer.
+ */
+struct PcmInt16DataSourceVersion1Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Quality used for sample rate conversion
+ SrcQuality src_quality;
+ /// Mix buffer index for decoded samples
+ s16 output_index;
+ /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ u16 flags;
+ /// Wavebuffer sample rate
+ u32 sample_rate;
+ /// Pitch used for sample rate conversion
+ f32 pitch;
+ /// Target channel to read within the wavebuffer
+ s8 channel_index;
+ /// Number of channels within the wavebuffer
+ s8 channel_count;
+ /// Wavebuffers containing the wavebuffer address, context address, looping information etc
+ std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
+ /// Voice state, updated each call and written back to game
+ CpuAddr voice_state;
+};
+
+/**
+ * AudioRenderer command to decode PCM s16-encoded version 2 wavebuffers
+ * into the output_index mix buffer.
+ */
+struct PcmInt16DataSourceVersion2Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Quality used for sample rate conversion
+ SrcQuality src_quality;
+ /// Mix buffer index for decoded samples
+ s16 output_index;
+ /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ u16 flags;
+ /// Wavebuffer sample rate
+ u32 sample_rate;
+ /// Pitch used for sample rate conversion
+ f32 pitch;
+ /// Target channel to read within the wavebuffer
+ s8 channel_index;
+ /// Number of channels within the wavebuffer
+ s8 channel_count;
+ /// Wavebuffers containing the wavebuffer address, context address, looping information etc
+ std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
+ /// Voice state, updated each call and written back to game
+ CpuAddr voice_state;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/aux_.cpp b/src/audio_core/renderer/command/effect/aux_.cpp
new file mode 100644
index 000000000..e76db893f
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/aux_.cpp
@@ -0,0 +1,207 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/aux_.h"
+#include "audio_core/renderer/effect/aux_.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Reset an AuxBuffer.
+ *
+ * @param memory - Core memory for writing.
+ * @param aux_info - Memory address pointing to the AuxInfo to reset.
+ */
+static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
+ if (aux_info == 0) {
+ LOG_ERROR(Service_Audio, "Aux info is 0!");
+ return;
+ }
+
+ auto info{reinterpret_cast<AuxInfo::AuxInfoDsp*>(memory.GetPointer(aux_info))};
+ info->read_offset = 0;
+ info->write_offset = 0;
+ info->total_sample_count = 0;
+}
+
+/**
+ * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
+ * update_count is set, to notify the game that an update happened.
+ *
+ * @param memory - Core memory for writing.
+ * @param send_info_ - Meta information for where to write the mix buffer.
+ * @param sample_count - Unused.
+ * @param send_buffer - Memory address to write the mix buffer to.
+ * @param count_max - Maximum number of samples in the receiving buffer.
+ * @param input - Input mix buffer to write.
+ * @param write_count_ - Number of samples to write.
+ * @param write_offset - Current offset to begin writing the receiving buffer at.
+ * @param update_count - If non-zero, send_info_ will be updated.
+ * @return Number of samples written.
+ */
+static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
+ [[maybe_unused]] u32 sample_count, const CpuAddr send_buffer,
+ const u32 count_max, std::span<const s32> input,
+ const u32 write_count_, const u32 write_offset,
+ const u32 update_count) {
+ if (write_count_ > count_max) {
+ LOG_ERROR(Service_Audio,
+ "write_count must be smaller than count_max! write_count {}, count_max {}",
+ write_count_, count_max);
+ return 0;
+ }
+
+ if (input.empty()) {
+ LOG_ERROR(Service_Audio, "input buffer is empty!");
+ return 0;
+ }
+
+ if (send_buffer == 0) {
+ LOG_ERROR(Service_Audio, "send_buffer is 0!");
+ return 0;
+ }
+
+ if (count_max == 0) {
+ return 0;
+ }
+
+ AuxInfo::AuxInfoDsp send_info{};
+ memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
+
+ u32 target_write_offset{send_info.write_offset + write_offset};
+ if (target_write_offset > count_max || write_count_ == 0) {
+ return 0;
+ }
+
+ u32 write_count{write_count_};
+ u32 write_pos{0};
+ while (write_count > 0) {
+ u32 to_write{std::min(count_max - target_write_offset, write_count)};
+
+ if (to_write > 0) {
+ memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
+ &input[write_pos], to_write * sizeof(s32));
+ }
+
+ target_write_offset = (target_write_offset + to_write) % count_max;
+ write_count -= to_write;
+ write_pos += to_write;
+ }
+
+ if (update_count) {
+ send_info.write_offset = (send_info.write_offset + update_count) % count_max;
+ }
+
+ memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
+
+ return write_count_;
+}
+
+/**
+ * Read the given memory at return_buffer into the output mix buffer, and update return_info_ if
+ * update_count is set, to notify the game that an update happened.
+ *
+ * @param memory - Core memory for writing.
+ * @param return_info_ - Meta information for where to read the mix buffer.
+ * @param return_buffer - Memory address to read the samples from.
+ * @param count_max - Maximum number of samples in the receiving buffer.
+ * @param output - Output mix buffer which will receive the samples.
+ * @param count_ - Number of samples to read.
+ * @param read_offset - Current offset to begin reading the return_buffer at.
+ * @param update_count - If non-zero, send_info_ will be updated.
+ * @return Number of samples read.
+ */
+static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr return_info_,
+ const CpuAddr return_buffer, const u32 count_max, std::span<s32> output,
+ const u32 count_, const u32 read_offset, const u32 update_count) {
+ if (count_max == 0) {
+ return 0;
+ }
+
+ if (count_ > count_max) {
+ LOG_ERROR(Service_Audio, "count must be smaller than count_max! count {}, count_max {}",
+ count_, count_max);
+ return 0;
+ }
+
+ if (output.empty()) {
+ LOG_ERROR(Service_Audio, "output buffer is empty!");
+ return 0;
+ }
+
+ if (return_buffer == 0) {
+ LOG_ERROR(Service_Audio, "return_buffer is 0!");
+ return 0;
+ }
+
+ AuxInfo::AuxInfoDsp return_info{};
+ memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
+
+ u32 target_read_offset{return_info.read_offset + read_offset};
+ if (target_read_offset > count_max) {
+ return 0;
+ }
+
+ u32 read_count{count_};
+ u32 read_pos{0};
+ while (read_count > 0) {
+ u32 to_read{std::min(count_max - target_read_offset, read_count)};
+
+ if (to_read > 0) {
+ memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32),
+ &output[read_pos], to_read * sizeof(s32));
+ }
+
+ target_read_offset = (target_read_offset + to_read) % count_max;
+ read_count -= to_read;
+ read_pos += to_read;
+ }
+
+ if (update_count) {
+ return_info.read_offset = (return_info.read_offset + update_count) % count_max;
+ }
+
+ memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
+
+ return count_;
+}
+
+void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled,
+ input, output);
+}
+
+void AuxCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto input_buffer{
+ processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
+ auto output_buffer{
+ processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
+
+ if (effect_enabled) {
+ WriteAuxBufferDsp(*processor.memory, send_buffer_info, processor.sample_count, send_buffer,
+ count_max, input_buffer, processor.sample_count, write_offset,
+ update_count);
+
+ auto read{ReadAuxBufferDsp(*processor.memory, return_buffer_info, return_buffer, count_max,
+ output_buffer, processor.sample_count, write_offset,
+ update_count)};
+
+ if (read != processor.sample_count) {
+ std::memset(&output_buffer[read], 0, processor.sample_count - read);
+ }
+ } else {
+ ResetAuxBufferDsp(*processor.memory, send_buffer_info);
+ ResetAuxBufferDsp(*processor.memory, return_buffer_info);
+ if (input != output) {
+ std::memcpy(output_buffer.data(), input_buffer.data(), output_buffer.size_bytes());
+ }
+ }
+}
+
+bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/aux_.h b/src/audio_core/renderer/command/effect/aux_.h
new file mode 100644
index 000000000..825c93732
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/aux_.h
@@ -0,0 +1,66 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game
+ * memory, and reading into the output buffer from game memory.
+ */
+struct AuxCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer index
+ s16 input;
+ /// Output mix buffer index
+ s16 output;
+ /// Meta info for writing
+ CpuAddr send_buffer_info;
+ /// Meta info for reading
+ CpuAddr return_buffer_info;
+ /// Game memory write buffer
+ CpuAddr send_buffer;
+ /// Game memory read buffer
+ CpuAddr return_buffer;
+ /// Max samples to read/write
+ u32 count_max;
+ /// Current read/write offset
+ u32 write_offset;
+ /// Number of samples to update per call
+ u32 update_count;
+ /// is this effect enabled?
+ bool effect_enabled;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/biquad_filter.cpp b/src/audio_core/renderer/command/effect/biquad_filter.cpp
new file mode 100644
index 000000000..1baae74fd
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/biquad_filter.cpp
@@ -0,0 +1,118 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/biquad_filter.h"
+#include "audio_core/renderer/voice/voice_state.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Biquad filter float implementation.
+ *
+ * @param output - Output container for filtered samples.
+ * @param input - Input container for samples to be filtered.
+ * @param b - Feedforward coefficients.
+ * @param a - Feedback coefficients.
+ * @param state - State to track previous samples between calls.
+ * @param sample_count - Number of samples to process.
+ */
+void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
+ std::array<s16, 3>& b_, std::array<s16, 2>& a_,
+ VoiceState::BiquadFilterState& state, const u32 sample_count) {
+ constexpr s64 min{std::numeric_limits<s32>::min()};
+ constexpr s64 max{std::numeric_limits<s32>::max()};
+ std::array<f64, 3> b{Common::FixedPoint<50, 14>::from_base(b_[0]).to_double(),
+ Common::FixedPoint<50, 14>::from_base(b_[1]).to_double(),
+ Common::FixedPoint<50, 14>::from_base(b_[2]).to_double()};
+ std::array<f64, 2> a{Common::FixedPoint<50, 14>::from_base(a_[0]).to_double(),
+ Common::FixedPoint<50, 14>::from_base(a_[1]).to_double()};
+ std::array<f64, 4> s{state.s0.to_double(), state.s1.to_double(), state.s2.to_double(),
+ state.s3.to_double()};
+
+ for (u32 i = 0; i < sample_count; i++) {
+ f64 in_sample{static_cast<f64>(input[i])};
+ auto sample{in_sample * b[0] + s[0] * b[1] + s[1] * b[2] + s[2] * a[0] + s[3] * a[1]};
+
+ output[i] = static_cast<s32>(std::clamp(static_cast<s64>(sample), min, max));
+
+ s[1] = s[0];
+ s[0] = in_sample;
+ s[3] = s[2];
+ s[2] = sample;
+ }
+
+ state.s0 = s[0];
+ state.s1 = s[1];
+ state.s2 = s[2];
+ state.s3 = s[3];
+}
+
+/**
+ * Biquad filter s32 implementation.
+ *
+ * @param output - Output container for filtered samples.
+ * @param input - Input container for samples to be filtered.
+ * @param b - Feedforward coefficients.
+ * @param a - Feedback coefficients.
+ * @param state - State to track previous samples between calls.
+ * @param sample_count - Number of samples to process.
+ */
+static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> input,
+ std::array<s16, 3>& b_, std::array<s16, 2>& a_,
+ VoiceState::BiquadFilterState& state, const u32 sample_count) {
+ constexpr s64 min{std::numeric_limits<s32>::min()};
+ constexpr s64 max{std::numeric_limits<s32>::max()};
+ std::array<Common::FixedPoint<50, 14>, 3> b{
+ Common::FixedPoint<50, 14>::from_base(b_[0]),
+ Common::FixedPoint<50, 14>::from_base(b_[1]),
+ Common::FixedPoint<50, 14>::from_base(b_[2]),
+ };
+ std::array<Common::FixedPoint<50, 14>, 3> a{
+ Common::FixedPoint<50, 14>::from_base(a_[0]),
+ Common::FixedPoint<50, 14>::from_base(a_[1]),
+ };
+
+ for (u32 i = 0; i < sample_count; i++) {
+ s64 in_sample{input[i]};
+ auto sample{in_sample * b[0] + state.s0};
+ const auto out_sample{std::clamp(sample.to_long(), min, max)};
+
+ output[i] = static_cast<s32>(out_sample);
+
+ state.s0 = state.s1 + b[1] * in_sample + a[0] * out_sample;
+ state.s1 = 0 + b[2] * in_sample + a[1] * out_sample;
+ }
+}
+
+void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format(
+ "BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n",
+ input, output, needs_init, use_float_processing);
+}
+
+void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)};
+ if (needs_init) {
+ std::memset(state_, 0, sizeof(VoiceState::BiquadFilterState));
+ }
+
+ auto input_buffer{
+ processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
+ auto output_buffer{
+ processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
+
+ if (use_float_processing) {
+ ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
+ processor.sample_count);
+ } else {
+ ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
+ processor.sample_count);
+ }
+}
+
+bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/biquad_filter.h b/src/audio_core/renderer/command/effect/biquad_filter.h
new file mode 100644
index 000000000..4c9c42d29
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/biquad_filter.h
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/voice/voice_info.h"
+#include "audio_core/renderer/voice/voice_state.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to
+ * the output mix buffer.
+ */
+struct BiquadFilterCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer index
+ s16 input;
+ /// Output mix buffer index
+ s16 output;
+ /// Input parameters for biquad
+ VoiceInfo::BiquadFilterParameter biquad;
+ /// Biquad state, updated each call
+ CpuAddr state;
+ /// If true, reset the state
+ bool needs_init;
+ /// If true, use float processing rather than int
+ bool use_float_processing;
+};
+
+/**
+ * Biquad filter float implementation.
+ *
+ * @param output - Output container for filtered samples.
+ * @param input - Input container for samples to be filtered.
+ * @param b - Feedforward coefficients.
+ * @param a - Feedback coefficients.
+ * @param state - State to track previous samples.
+ * @param sample_count - Number of samples to process.
+ */
+void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
+ std::array<s16, 3>& b, std::array<s16, 2>& a,
+ VoiceState::BiquadFilterState& state, const u32 sample_count);
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/capture.cpp b/src/audio_core/renderer/command/effect/capture.cpp
new file mode 100644
index 000000000..042fd286e
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/capture.cpp
@@ -0,0 +1,142 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/capture.h"
+#include "audio_core/renderer/effect/aux_.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Reset an AuxBuffer.
+ *
+ * @param memory - Core memory for writing.
+ * @param aux_info - Memory address pointing to the AuxInfo to reset.
+ */
+static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
+ if (aux_info == 0) {
+ LOG_ERROR(Service_Audio, "Aux info is 0!");
+ return;
+ }
+
+ memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, read_offset)), 0);
+ memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, write_offset)), 0);
+ memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, total_sample_count)), 0);
+}
+
+/**
+ * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
+ * update_count is set, to notify the game that an update happened.
+ *
+ * @param memory - Core memory for writing.
+ * @param send_info_ - Header information for where to write the mix buffer.
+ * @param send_buffer - Memory address to write the mix buffer to.
+ * @param count_max - Maximum number of samples in the receiving buffer.
+ * @param input - Input mix buffer to write.
+ * @param write_count_ - Number of samples to write.
+ * @param write_offset - Current offset to begin writing the receiving buffer at.
+ * @param update_count - If non-zero, send_info_ will be updated.
+ * @return Number of samples written.
+ */
+static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
+ const CpuAddr send_buffer, u32 count_max, std::span<const s32> input,
+ const u32 write_count_, const u32 write_offset,
+ const u32 update_count) {
+ if (write_count_ > count_max) {
+ LOG_ERROR(Service_Audio,
+ "write_count must be smaller than count_max! write_count {}, count_max {}",
+ write_count_, count_max);
+ return 0;
+ }
+
+ if (send_info_ == 0) {
+ LOG_ERROR(Service_Audio, "send_info is 0!");
+ return 0;
+ }
+
+ if (input.empty()) {
+ LOG_ERROR(Service_Audio, "input buffer is empty!");
+ return 0;
+ }
+
+ if (send_buffer == 0) {
+ LOG_ERROR(Service_Audio, "send_buffer is 0!");
+ return 0;
+ }
+
+ if (count_max == 0) {
+ return 0;
+ }
+
+ AuxInfo::AuxBufferInfo send_info{};
+ memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
+
+ u32 target_write_offset{send_info.dsp_info.write_offset + write_offset};
+ if (target_write_offset > count_max || write_count_ == 0) {
+ return 0;
+ }
+
+ u32 write_count{write_count_};
+ u32 write_pos{0};
+ while (write_count > 0) {
+ u32 to_write{std::min(count_max - target_write_offset, write_count)};
+
+ if (to_write > 0) {
+ memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
+ &input[write_pos], to_write * sizeof(s32));
+ }
+
+ target_write_offset = (target_write_offset + to_write) % count_max;
+ write_count -= to_write;
+ write_pos += to_write;
+ }
+
+ if (update_count) {
+ const auto count_diff{send_info.dsp_info.total_sample_count -
+ send_info.cpu_info.total_sample_count};
+ if (count_diff >= count_max) {
+ auto dsp_lost_count{send_info.dsp_info.lost_sample_count + update_count};
+ if (dsp_lost_count - send_info.cpu_info.lost_sample_count <
+ send_info.dsp_info.lost_sample_count - send_info.cpu_info.lost_sample_count) {
+ dsp_lost_count = send_info.cpu_info.lost_sample_count - 1;
+ }
+ send_info.dsp_info.lost_sample_count = dsp_lost_count;
+ }
+
+ send_info.dsp_info.write_offset =
+ (send_info.dsp_info.write_offset + update_count + count_max) % count_max;
+
+ auto new_sample_count{send_info.dsp_info.total_sample_count + update_count};
+ if (new_sample_count - send_info.cpu_info.total_sample_count < count_diff) {
+ new_sample_count = send_info.cpu_info.total_sample_count - 1;
+ }
+ send_info.dsp_info.total_sample_count = new_sample_count;
+ }
+
+ memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
+
+ return write_count_;
+}
+
+void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled,
+ input, output);
+}
+
+void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) {
+ if (effect_enabled) {
+ auto input_buffer{
+ processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
+ WriteAuxBufferDsp(*processor.memory, send_buffer_info, send_buffer, count_max, input_buffer,
+ processor.sample_count, write_offset, update_count);
+ } else {
+ ResetAuxBufferDsp(*processor.memory, send_buffer_info);
+ }
+}
+
+bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/capture.h b/src/audio_core/renderer/command/effect/capture.h
new file mode 100644
index 000000000..8670acb24
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/capture.h
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory
+ * address.
+ */
+struct CaptureCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer index
+ s16 input;
+ /// Output mix buffer index
+ s16 output;
+ /// Meta info for writing
+ CpuAddr send_buffer_info;
+ /// Game memory write buffer
+ CpuAddr send_buffer;
+ /// Max samples to read/write
+ u32 count_max;
+ /// Current read/write offset
+ u32 write_offset;
+ /// Number of samples to update per call
+ u32 update_count;
+ /// is this effect enabled?
+ bool effect_enabled;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp
new file mode 100644
index 000000000..7229618e8
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/compressor.cpp
@@ -0,0 +1,155 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <cmath>
+#include <span>
+#include <vector>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/compressor.h"
+#include "audio_core/renderer/effect/compressor.h"
+
+namespace AudioCore::AudioRenderer {
+
+static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params,
+ CompressorInfo::State& state) {
+ const auto ratio{1.0f / params.compressor_ratio};
+ auto makeup_gain{0.0f};
+ if (params.makeup_gain_enabled) {
+ makeup_gain = (params.threshold * 0.5f) * (ratio - 1.0f) - 3.0f;
+ }
+ state.makeup_gain = makeup_gain;
+ state.unk_18 = params.unk_28;
+
+ const auto a{(params.out_gain + makeup_gain) / 20.0f * 3.3219f};
+ const auto b{(a - std::trunc(a)) * 0.69315f};
+ const auto c{std::pow(2.0f, b)};
+
+ state.unk_0C = (1.0f - ratio) / 6.0f;
+ state.unk_14 = params.threshold + 1.5f;
+ state.unk_10 = params.threshold - 1.5f;
+ state.unk_20 = c;
+}
+
+static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2& params,
+ CompressorInfo::State& state) {
+ state = {};
+
+ state.unk_00 = 0;
+ state.unk_04 = 1.0f;
+ state.unk_08 = 1.0f;
+
+ SetCompressorEffectParameter(params, state);
+}
+
+static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params,
+ CompressorInfo::State& state, bool enabled,
+ std::vector<std::span<const s32>> input_buffers,
+ std::vector<std::span<s32>> output_buffers, u32 sample_count) {
+ if (enabled) {
+ auto state_00{state.unk_00};
+ auto state_04{state.unk_04};
+ auto state_08{state.unk_08};
+ auto state_18{state.unk_18};
+
+ for (u32 i = 0; i < sample_count; i++) {
+ auto a{0.0f};
+ for (s16 channel = 0; channel < params.channel_count; channel++) {
+ const auto input_sample{Common::FixedPoint<49, 15>(input_buffers[channel][i])};
+ a += (input_sample * input_sample).to_float();
+ }
+
+ state_00 += params.unk_24 * ((a / params.channel_count) - state.unk_00);
+
+ auto b{-100.0f};
+ auto c{0.0f};
+ if (state_00 >= 1.0e-10) {
+ b = std::log10(state_00) * 10.0f;
+ c = 1.0f;
+ }
+
+ if (b >= state.unk_10) {
+ const auto d{b >= state.unk_14
+ ? ((1.0f / params.compressor_ratio) - 1.0f) *
+ (b - params.threshold)
+ : (b - state.unk_10) * (b - state.unk_10) * -state.unk_0C};
+ const auto e{d / 20.0f * 3.3219f};
+ const auto f{(e - std::trunc(e)) * 0.69315f};
+ c = std::pow(2.0f, f);
+ }
+
+ state_18 = params.unk_28;
+ auto tmp{c};
+ if ((state_04 - c) <= 0.08f) {
+ state_18 = params.unk_2C;
+ if (((state_04 - c) >= -0.08f) && (std::abs(state_08 - c) >= 0.001f)) {
+ tmp = state_04;
+ }
+ }
+
+ state_04 = tmp;
+ state_08 += (c - state_08) * state_18;
+
+ for (s16 channel = 0; channel < params.channel_count; channel++) {
+ output_buffers[channel][i] = static_cast<s32>(
+ static_cast<f32>(input_buffers[channel][i]) * state_08 * state.unk_20);
+ }
+ }
+
+ state.unk_00 = state_00;
+ state.unk_04 = state_04;
+ state.unk_08 = state_08;
+ state.unk_18 = state_18;
+ } else {
+ for (s16 channel = 0; channel < params.channel_count; channel++) {
+ if (params.inputs[channel] != params.outputs[channel]) {
+ std::memcpy(output_buffers[channel].data(), input_buffers[channel].data(),
+ output_buffers[channel].size_bytes());
+ }
+ }
+ }
+}
+
+void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
+ for (s16 i = 0; i < parameter.channel_count; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (s16 i = 0; i < parameter.channel_count; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
+ std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
+ std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+
+ for (s16 i = 0; i < parameter.channel_count; i++) {
+ input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count);
+ output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count);
+ }
+
+ auto state_{reinterpret_cast<CompressorInfo::State*>(state)};
+
+ if (effect_enabled) {
+ if (parameter.state == CompressorInfo::ParameterState::Updating) {
+ SetCompressorEffectParameter(parameter, *state_);
+ } else if (parameter.state == CompressorInfo::ParameterState::Initialized) {
+ InitializeCompressorEffect(parameter, *state_);
+ }
+ }
+
+ ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
+ processor.sample_count);
+}
+
+bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/compressor.h b/src/audio_core/renderer/command/effect/compressor.h
new file mode 100644
index 000000000..f8e96cb43
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/compressor.h
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/effect/compressor.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for limiting volume between a high and low threshold.
+ * Version 1.
+ */
+struct CompressorCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Input parameters
+ CompressorInfo::ParameterVersion2 parameter;
+ /// State, updated each call
+ CpuAddr state;
+ /// Game-supplied workbuffer (Unused)
+ CpuAddr workbuffer;
+ /// Is this effect enabled?
+ bool effect_enabled;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/delay.cpp b/src/audio_core/renderer/command/effect/delay.cpp
new file mode 100644
index 000000000..a4e408d40
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/delay.cpp
@@ -0,0 +1,238 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/delay.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Update the DelayInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ */
+static void SetDelayEffectParameter(const DelayInfo::ParameterVersion1& params,
+ DelayInfo::State& state) {
+ auto channel_spread{params.channel_spread};
+ state.feedback_gain = params.feedback_gain * 0.97998046875f;
+ state.delay_feedback_gain = state.feedback_gain * (1.0f - channel_spread);
+ if (params.channel_count == 4 || params.channel_count == 6) {
+ channel_spread >>= 1;
+ }
+ state.delay_feedback_cross_gain = channel_spread * state.feedback_gain;
+ state.lowpass_feedback_gain = params.lowpass_amount * 0.949951171875f;
+ state.lowpass_gain = 1.0f - state.lowpass_feedback_gain;
+}
+
+/**
+ * Initialize a new DelayInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ * @param workbuffer - Game-supplied memory for the state. (Unused)
+ */
+static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
+ DelayInfo::State& state,
+ [[maybe_unused]] const CpuAddr workbuffer) {
+ state = {};
+
+ for (u32 channel = 0; channel < params.channel_count; channel++) {
+ Common::FixedPoint<32, 32> sample_count_max{0.064f};
+ sample_count_max *= params.sample_rate.to_int_floor() * params.delay_time_max;
+
+ Common::FixedPoint<18, 14> delay_time{params.delay_time};
+ delay_time *= params.sample_rate / 1000;
+ Common::FixedPoint<32, 32> sample_count{delay_time};
+
+ if (sample_count > sample_count_max) {
+ sample_count = sample_count_max;
+ }
+
+ state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor();
+ state.delay_lines[channel].sample_count = sample_count.to_int_floor();
+ state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0);
+ if (state.delay_lines[channel].buffer.size() == 0) {
+ state.delay_lines[channel].buffer.push_back(0);
+ }
+ state.delay_lines[channel].buffer_pos = 0;
+ state.delay_lines[channel].decay_rate = 1.0f;
+ }
+
+ SetDelayEffectParameter(params, state);
+}
+
+/**
+ * Delay effect impl, according to the parameters and current state, on the input mix buffers,
+ * saving the results to the output mix buffers.
+ *
+ * @tparam NumChannels - Number of channels to process. 1-6.
+ * @param params - Input parameters to use.
+ * @param state - State to use, must be initialized (see InitializeDelayEffect).
+ * @param inputs - Input mix buffers to performan the delay on.
+ * @param outputs - Output mix buffers to receive the delayed samples.
+ * @param sample_count - Number of samples to process.
+ */
+template <size_t NumChannels>
+static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
+ std::vector<std::span<const s32>>& inputs,
+ std::vector<std::span<s32>>& outputs, const u32 sample_count) {
+ for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
+ std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{};
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ input_samples[channel] = inputs[channel][sample_index] * 64;
+ }
+
+ std::array<Common::FixedPoint<50, 14>, NumChannels> delay_samples{};
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ delay_samples[channel] = state.delay_lines[channel].Read();
+ }
+
+ // clang-format off
+ std::array<std::array<Common::FixedPoint<18, 14>, NumChannels>, NumChannels> matrix{};
+ if constexpr (NumChannels == 1) {
+ matrix = {{
+ {state.feedback_gain},
+ }};
+ } else if constexpr (NumChannels == 2) {
+ matrix = {{
+ {state.delay_feedback_gain, state.delay_feedback_cross_gain},
+ {state.delay_feedback_cross_gain, state.delay_feedback_gain},
+ }};
+ } else if constexpr (NumChannels == 4) {
+ matrix = {{
+ {state.delay_feedback_gain, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, 0.0f},
+ {state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain},
+ {state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
+ {0.0f, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain},
+ }};
+ } else if constexpr (NumChannels == 6) {
+ matrix = {{
+ {state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f},
+ {0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain},
+ {state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, 0.0f, 0.0f},
+ {0.0f, 0.0f, 0.0f, params.feedback_gain, 0.0f, 0.0f},
+ {state.delay_feedback_cross_gain, 0.0f, 0.0f, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
+ {0.0f, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain, state.delay_feedback_gain},
+ }};
+ }
+ // clang-format on
+
+ std::array<Common::FixedPoint<50, 14>, NumChannels> gained_samples{};
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ Common::FixedPoint<50, 14> delay{};
+ for (u32 j = 0; j < NumChannels; j++) {
+ delay += delay_samples[j] * matrix[j][channel];
+ }
+ gained_samples[channel] = input_samples[channel] * params.in_gain + delay;
+ }
+
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ state.lowpass_z[channel] = gained_samples[channel] * state.lowpass_gain +
+ state.lowpass_z[channel] * state.lowpass_feedback_gain;
+ state.delay_lines[channel].Write(state.lowpass_z[channel]);
+ }
+
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ outputs[channel][sample_index] = (input_samples[channel] * params.dry_gain +
+ delay_samples[channel] * params.wet_gain)
+ .to_int_floor() /
+ 64;
+ }
+ }
+}
+
+/**
+ * Apply a delay effect if enabled, according to the parameters and current state, on the input mix
+ * buffers, saving the results to the output mix buffers.
+ *
+ * @param params - Input parameters to use.
+ * @param state - State to use, must be initialized (see InitializeDelayEffect).
+ * @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
+ * @param inputs - Input mix buffers to performan the delay on.
+ * @param outputs - Output mix buffers to receive the delayed samples.
+ * @param sample_count - Number of samples to process.
+ */
+static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
+ const bool enabled, std::vector<std::span<const s32>>& inputs,
+ std::vector<std::span<s32>>& outputs, const u32 sample_count) {
+
+ if (!IsChannelCountValid(params.channel_count)) {
+ LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count);
+ return;
+ }
+
+ if (enabled) {
+ switch (params.channel_count) {
+ case 1:
+ ApplyDelay<1>(params, state, inputs, outputs, sample_count);
+ break;
+ case 2:
+ ApplyDelay<2>(params, state, inputs, outputs, sample_count);
+ break;
+ case 4:
+ ApplyDelay<4>(params, state, inputs, outputs, sample_count);
+ break;
+ case 6:
+ ApplyDelay<6>(params, state, inputs, outputs, sample_count);
+ break;
+ default:
+ for (u32 channel = 0; channel < params.channel_count; channel++) {
+ if (inputs[channel].data() != outputs[channel].data()) {
+ std::memcpy(outputs[channel].data(), inputs[channel].data(),
+ sample_count * sizeof(s32));
+ }
+ }
+ break;
+ }
+ } else {
+ for (u32 channel = 0; channel < params.channel_count; channel++) {
+ if (inputs[channel].data() != outputs[channel].data()) {
+ std::memcpy(outputs[channel].data(), inputs[channel].data(),
+ sample_count * sizeof(s32));
+ }
+ }
+ }
+}
+
+void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void DelayCommand::Process(const ADSP::CommandListProcessor& processor) {
+ std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
+ std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+
+ for (s16 i = 0; i < parameter.channel_count; i++) {
+ input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count);
+ output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count);
+ }
+
+ auto state_{reinterpret_cast<DelayInfo::State*>(state)};
+
+ if (effect_enabled) {
+ if (parameter.state == DelayInfo::ParameterState::Updating) {
+ SetDelayEffectParameter(parameter, *state_);
+ } else if (parameter.state == DelayInfo::ParameterState::Initialized) {
+ InitializeDelayEffect(parameter, *state_, workbuffer);
+ }
+ }
+ ApplyDelayEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
+ processor.sample_count);
+}
+
+bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/delay.h b/src/audio_core/renderer/command/effect/delay.h
new file mode 100644
index 000000000..b7a15ae6b
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/delay.h
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/effect/delay.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters
+ * and state, outputs receives the delayed samples.
+ */
+struct DelayCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Input parameters
+ DelayInfo::ParameterVersion1 parameter;
+ /// State, updated each call
+ CpuAddr state;
+ /// Game-supplied workbuffer (Unused)
+ CpuAddr workbuffer;
+ /// Is this effect enabled?
+ bool effect_enabled;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp
new file mode 100644
index 000000000..c4bf3943a
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp
@@ -0,0 +1,437 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <numbers>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
+
+namespace AudioCore::AudioRenderer {
+
+constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{
+ 5.0f,
+ 6.0f,
+ 13.0f,
+ 14.0f,
+};
+constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MaxDelayLineTimes{
+ 45.7042007446f,
+ 82.7817001343f,
+ 149.938293457f,
+ 271.575805664f,
+};
+constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay0MaxDelayLineTimes{17.0f, 13.0f,
+ 9.0f, 7.0f};
+constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay1MaxDelayLineTimes{19.0f, 11.0f,
+ 10.0f, 6.0f};
+constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyTapTimes{
+ 0.0171360000968f,
+ 0.0591540001333f,
+ 0.161733001471f,
+ 0.390186011791f,
+ 0.425262004137f,
+ 0.455410987139f,
+ 0.689737021923f,
+ 0.74590998888f,
+ 0.833844006062f,
+ 0.859502017498f,
+ 0.0f,
+ 0.0750240013003f,
+ 0.168788000941f,
+ 0.299901008606f,
+ 0.337442994118f,
+ 0.371903002262f,
+ 0.599011003971f,
+ 0.716741025448f,
+ 0.817858994007f,
+ 0.85166400671f,
+};
+
+constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyGains{
+ 0.67096f, 0.61027f, 1.0f, 0.3568f, 0.68361f, 0.65978f, 0.51939f,
+ 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.3827f,
+ 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f};
+
+/**
+ * Update the I3dl2ReverbInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ * @param reset - If enabled, the state buffers will be reset. Only set this on initialize.
+ */
+static void UpdateI3dl2ReverbEffectParameter(const I3dl2ReverbInfo::ParameterVersion1& params,
+ I3dl2ReverbInfo::State& state, const bool reset) {
+ const auto pow_10 = [](f32 val) -> f32 {
+ return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
+ };
+ const auto sin = [](f32 degrees) -> f32 {
+ return std::sin(degrees * std::numbers::pi_v<f32> / 180.0f);
+ };
+ const auto cos = [](f32 degrees) -> f32 {
+ return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
+ };
+
+ Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000.0f};
+
+ state.dry_gain = params.dry_gain;
+ Common::FixedPoint<50, 14> early_gain{
+ std::min(params.room_gain + params.reflection_gain, 5000.0f) / 2000.0f};
+ state.early_gain = pow_10(early_gain.to_float());
+ Common::FixedPoint<50, 14> late_gain{std::min(params.room_gain + params.reverb_gain, 5000.0f) /
+ 2000.0f};
+ state.late_gain = pow_10(late_gain.to_float());
+
+ Common::FixedPoint<50, 14> hf_gain{pow_10(params.room_HF_gain / 2000.0f)};
+ if (hf_gain >= 1.0f) {
+ state.lowpass_1 = 0.0f;
+ state.lowpass_2 = 1.0f;
+ } else {
+ const auto reference_hf{(params.reference_HF * 256.0f) /
+ static_cast<f32>(params.sample_rate)};
+ const Common::FixedPoint<50, 14> a{1.0f - hf_gain.to_float()};
+ const Common::FixedPoint<50, 14> b{2.0f + (-cos(reference_hf) * (hf_gain * 2.0f))};
+ const Common::FixedPoint<50, 14> c{
+ std::sqrt(std::pow(b.to_float(), 2.0f) + (std::pow(a.to_float(), 2.0f) * -4.0f))};
+
+ state.lowpass_1 = std::min(((b - c) / (a * 2.0f)).to_float(), 0.99723f);
+ state.lowpass_2 = 1.0f - state.lowpass_1;
+ }
+
+ state.early_to_late_taps =
+ (((params.reflection_delay + params.late_reverb_delay_time) * 1000.0f) * delay).to_int();
+ state.last_reverb_echo = params.late_reverb_diffusion * 0.6f * 0.01f;
+
+ for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
+ auto curr_delay{
+ ((MinDelayLineTimes[i] + (params.late_reverb_density / 100.0f) *
+ (MaxDelayLineTimes[i] - MinDelayLineTimes[i])) *
+ delay)
+ .to_int()};
+ state.fdn_delay_lines[i].SetDelay(curr_delay);
+
+ const auto a{
+ (static_cast<f32>(state.fdn_delay_lines[i].delay + state.decay_delay_lines0[i].delay +
+ state.decay_delay_lines1[i].delay) *
+ -60.0f) /
+ (params.late_reverb_decay_time * static_cast<f32>(params.sample_rate))};
+ const auto b{a / params.late_reverb_HF_decay_ratio};
+ const auto c{
+ cos(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate)) /
+ sin(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate))};
+ const auto d{pow_10((b - a) / 40.0f)};
+ const auto e{pow_10((b + a) / 40.0f) * 0.7071f};
+
+ state.lowpass_coeff[i][0] = ((c * d + 1.0f) * e) / (c + d);
+ state.lowpass_coeff[i][1] = ((1.0f - (c * d)) * e) / (c + d);
+ state.lowpass_coeff[i][2] = (c - d) / (c + d);
+
+ state.decay_delay_lines0[i].wet_gain = state.last_reverb_echo;
+ state.decay_delay_lines1[i].wet_gain = state.last_reverb_echo * -0.9f;
+ }
+
+ if (reset) {
+ state.shelf_filter.fill(0.0f);
+ state.lowpass_0 = 0.0f;
+ for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
+ std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
+ std::ranges::fill(state.decay_delay_lines0[i].buffer, 0);
+ std::ranges::fill(state.decay_delay_lines1[i].buffer, 0);
+ }
+ std::ranges::fill(state.center_delay_line.buffer, 0);
+ std::ranges::fill(state.early_delay_line.buffer, 0);
+ }
+
+ const auto reflection_time{(params.late_reverb_delay_time * 0.9998f + 0.02f) * 1000.0f};
+ const auto reflection_delay{params.reflection_delay * 1000.0f};
+ for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayTaps; i++) {
+ auto length{((reflection_delay + reflection_time * EarlyTapTimes[i]) * delay).to_int()};
+ if (length >= state.early_delay_line.max_delay) {
+ length = state.early_delay_line.max_delay;
+ }
+ state.early_tap_steps[i] = length;
+ }
+}
+
+/**
+ * Initialize a new I3dl2ReverbInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ * @param workbuffer - Game-supplied memory for the state. (Unused)
+ */
+static void InitializeI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
+ I3dl2ReverbInfo::State& state, const CpuAddr workbuffer) {
+ state = {};
+ Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000};
+
+ for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
+ auto fdn_delay_time{(MaxDelayLineTimes[i] * delay).to_uint_floor()};
+ state.fdn_delay_lines[i].Initialize(fdn_delay_time);
+
+ auto decay0_delay_time{(Decay0MaxDelayLineTimes[i] * delay).to_uint_floor()};
+ state.decay_delay_lines0[i].Initialize(decay0_delay_time);
+
+ auto decay1_delay_time{(Decay1MaxDelayLineTimes[i] * delay).to_uint_floor()};
+ state.decay_delay_lines1[i].Initialize(decay1_delay_time);
+ }
+
+ const auto center_delay_time{(5 * delay).to_uint_floor()};
+ state.center_delay_line.Initialize(center_delay_time);
+
+ const auto early_delay_time{(400 * delay).to_uint_floor()};
+ state.early_delay_line.Initialize(early_delay_time);
+
+ UpdateI3dl2ReverbEffectParameter(params, state, true);
+}
+
+/**
+ * Pass-through the effect, copying input to output directly, with no reverb applied.
+ *
+ * @param inputs - Array of input mix buffers to copy.
+ * @param outputs - Array of output mix buffers to receive copy.
+ * @param channel_count - Number of channels in inputs and outputs.
+ * @param sample_count - Number of samples within each channel (unused).
+ */
+static void ApplyI3dl2ReverbEffectBypass(std::span<std::span<const s32>> inputs,
+ std::span<std::span<s32>> outputs, const u32 channel_count,
+ [[maybe_unused]] const u32 sample_count) {
+ for (u32 i = 0; i < channel_count; i++) {
+ if (inputs[i].data() != outputs[i].data()) {
+ std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
+ }
+ }
+}
+
+/**
+ * Tick the delay lines, reading and returning their current output, and writing a new decaying
+ * sample (mix).
+ *
+ * @param decay0 - The first decay line.
+ * @param decay1 - The second decay line.
+ * @param fdn - Feedback delay network.
+ * @param mix - The new calculated sample to be written and decayed.
+ * @return The next delayed and decayed sample.
+ */
+static Common::FixedPoint<50, 14> Axfx2AllPassTick(I3dl2ReverbInfo::I3dl2DelayLine& decay0,
+ I3dl2ReverbInfo::I3dl2DelayLine& decay1,
+ I3dl2ReverbInfo::I3dl2DelayLine& fdn,
+ const Common::FixedPoint<50, 14> mix) {
+ auto val{decay0.Read()};
+ auto mixed{mix - (val * decay0.wet_gain)};
+ auto out{decay0.Tick(mixed) + (mixed * decay0.wet_gain)};
+
+ val = decay1.Read();
+ mixed = out - (val * decay1.wet_gain);
+ out = decay1.Tick(mixed) + (mixed * decay1.wet_gain);
+
+ fdn.Tick(out);
+ return out;
+}
+
+/**
+ * Impl. Apply a I3DL2 reverb according to the current state, on the input mix buffers,
+ * saving the results to the output mix buffers.
+ *
+ * @tparam NumChannels - Number of channels to process. 1-6.
+ Inputs/outputs should have this many buffers.
+ * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
+ * @param inputs - Input mix buffers to perform the reverb on.
+ * @param outputs - Output mix buffers to receive the reverbed samples.
+ * @param sample_count - Number of samples to process.
+ */
+template <size_t NumChannels>
+static void ApplyI3dl2ReverbEffect(I3dl2ReverbInfo::State& state,
+ std::span<std::span<const s32>> inputs,
+ std::span<std::span<s32>> outputs, const u32 sample_count) {
+ constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
+ 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
+ };
+ constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
+ 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3,
+ };
+ constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
+ 2, 0, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 1, 0, 0, 0, 0, 5, 5, 5,
+ };
+
+ std::span<const u8> tap_indexes{};
+ if constexpr (NumChannels == 1) {
+ tap_indexes = OutTapIndexes1Ch;
+ } else if constexpr (NumChannels == 2) {
+ tap_indexes = OutTapIndexes2Ch;
+ } else if constexpr (NumChannels == 4) {
+ tap_indexes = OutTapIndexes4Ch;
+ } else if constexpr (NumChannels == 6) {
+ tap_indexes = OutTapIndexes6Ch;
+ }
+
+ for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
+ Common::FixedPoint<50, 14> early_to_late_tap{
+ state.early_delay_line.TapOut(state.early_to_late_taps)};
+ std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
+
+ for (u32 early_tap = 0; early_tap < I3dl2ReverbInfo::MaxDelayTaps; early_tap++) {
+ output_samples[tap_indexes[early_tap]] +=
+ state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
+ EarlyGains[early_tap];
+ if constexpr (NumChannels == 6) {
+ output_samples[static_cast<u32>(Channels::LFE)] +=
+ state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
+ EarlyGains[early_tap];
+ }
+ }
+
+ Common::FixedPoint<50, 14> current_sample{};
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ current_sample += inputs[channel][sample_index];
+ }
+
+ state.lowpass_0 =
+ (current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1).to_float();
+ state.early_delay_line.Tick(state.lowpass_0);
+
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ output_samples[channel] *= state.early_gain;
+ }
+
+ std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> filtered_samples{};
+ for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
+ filtered_samples[delay_line] =
+ state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][0] +
+ state.shelf_filter[delay_line];
+ state.shelf_filter[delay_line] =
+ (filtered_samples[delay_line] * state.lowpass_coeff[delay_line][2] +
+ state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][1])
+ .to_float();
+ }
+
+ const std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> mix_matrix{
+ filtered_samples[1] + filtered_samples[2] + early_to_late_tap * state.late_gain,
+ -filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
+ filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
+ filtered_samples[1] - filtered_samples[2] + early_to_late_tap * state.late_gain,
+ };
+
+ std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> allpass_samples{};
+ for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
+ allpass_samples[delay_line] = Axfx2AllPassTick(
+ state.decay_delay_lines0[delay_line], state.decay_delay_lines1[delay_line],
+ state.fdn_delay_lines[delay_line], mix_matrix[delay_line]);
+ }
+
+ if constexpr (NumChannels == 6) {
+ const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
+ allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
+ allpass_samples[3], allpass_samples[2], allpass_samples[3],
+ };
+
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ Common::FixedPoint<50, 14> allpass{};
+
+ if (channel == static_cast<u32>(Channels::Center)) {
+ allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
+ } else {
+ allpass = allpass_outputs[channel];
+ }
+
+ auto out_sample{output_samples[channel] + allpass +
+ state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
+
+ outputs[channel][sample_index] =
+ static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
+ }
+ } else {
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ auto out_sample{output_samples[channel] + allpass_samples[channel] +
+ state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
+ outputs[channel][sample_index] =
+ static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
+ }
+ }
+ }
+}
+
+/**
+ * Apply a I3DL2 reverb if enabled, according to the current state, on the input mix buffers,
+ * saving the results to the output mix buffers.
+ *
+ * @param params - Input parameters to use.
+ * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
+ * @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
+ * @param inputs - Input mix buffers to performan the delay on.
+ * @param outputs - Output mix buffers to receive the delayed samples.
+ * @param sample_count - Number of samples to process.
+ */
+static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
+ I3dl2ReverbInfo::State& state, const bool enabled,
+ std::span<std::span<const s32>> inputs,
+ std::span<std::span<s32>> outputs, const u32 sample_count) {
+ if (enabled) {
+ switch (params.channel_count) {
+ case 0:
+ return;
+ case 1:
+ ApplyI3dl2ReverbEffect<1>(state, inputs, outputs, sample_count);
+ break;
+ case 2:
+ ApplyI3dl2ReverbEffect<2>(state, inputs, outputs, sample_count);
+ break;
+ case 4:
+ ApplyI3dl2ReverbEffect<4>(state, inputs, outputs, sample_count);
+ break;
+ case 6:
+ ApplyI3dl2ReverbEffect<6>(state, inputs, outputs, sample_count);
+ break;
+ default:
+ ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
+ break;
+ }
+ } else {
+ ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
+ }
+}
+
+void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
+ for (u32 i = 0; i < parameter.channel_count; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (u32 i = 0; i < parameter.channel_count; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
+ std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
+ std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+
+ for (u32 i = 0; i < parameter.channel_count; i++) {
+ input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count);
+ output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count);
+ }
+
+ auto state_{reinterpret_cast<I3dl2ReverbInfo::State*>(state)};
+
+ if (effect_enabled) {
+ if (parameter.state == I3dl2ReverbInfo::ParameterState::Updating) {
+ UpdateI3dl2ReverbEffectParameter(parameter, *state_, false);
+ } else if (parameter.state == I3dl2ReverbInfo::ParameterState::Initialized) {
+ InitializeI3dl2ReverbEffect(parameter, *state_, workbuffer);
+ }
+ }
+ ApplyI3dl2ReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
+ processor.sample_count);
+}
+
+bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.h b/src/audio_core/renderer/command/effect/i3dl2_reverb.h
new file mode 100644
index 000000000..243877056
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.h
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/effect/i3dl2.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to
+ * the I3DL2 spec, outputs receives the results.
+ */
+struct I3dl2ReverbCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Input parameters
+ I3dl2ReverbInfo::ParameterVersion1 parameter;
+ /// State, updated each call
+ CpuAddr state;
+ /// Game-supplied workbuffer (Unused)
+ CpuAddr workbuffer;
+ /// Is this effect enabled?
+ bool effect_enabled;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/light_limiter.cpp b/src/audio_core/renderer/command/effect/light_limiter.cpp
new file mode 100644
index 000000000..e8fb0e2fc
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/light_limiter.cpp
@@ -0,0 +1,222 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/light_limiter.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Update the LightLimiterInfo state according to the given parameters.
+ * A no-op.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ */
+static void UpdateLightLimiterEffectParameter(const LightLimiterInfo::ParameterVersion2& params,
+ LightLimiterInfo::State& state) {}
+
+/**
+ * Initialize a new LightLimiterInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ * @param workbuffer - Game-supplied memory for the state. (Unused)
+ */
+static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
+ LightLimiterInfo::State& state, const CpuAddr workbuffer) {
+ state = {};
+ state.samples_average.fill(0.0f);
+ state.compression_gain.fill(1.0f);
+ state.look_ahead_sample_offsets.fill(0);
+ for (u32 i = 0; i < params.channel_count; i++) {
+ state.look_ahead_sample_buffers[i].resize(params.look_ahead_samples_max, 0.0f);
+ }
+}
+
+/**
+ * Apply a light limiter effect if enabled, according to the current state, on the input mix
+ * buffers, saving the results to the output mix buffers.
+ *
+ * @param params - Input parameters to use.
+ * @param state - State to use, must be initialized (see InitializeLightLimiterEffect).
+ * @param enabled - If enabled, limiter will be applied, otherwise input is copied to output.
+ * @param inputs - Input mix buffers to perform the limiter on.
+ * @param outputs - Output mix buffers to receive the limited samples.
+ * @param sample_count - Number of samples to process.
+ * @params statistics - Optional output statistics, only used with version 2.
+ */
+static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
+ LightLimiterInfo::State& state, const bool enabled,
+ std::vector<std::span<const s32>>& inputs,
+ std::vector<std::span<s32>>& outputs, const u32 sample_count,
+ LightLimiterInfo::StatisticsInternal* statistics) {
+ constexpr s64 min{std::numeric_limits<s32>::min()};
+ constexpr s64 max{std::numeric_limits<s32>::max()};
+
+ const auto recip_estimate = [](f64 a) -> f64 {
+ s32 q, s;
+ f64 r;
+ q = (s32)(a * 512.0); /* a in units of 1/512 rounded down */
+ r = 1.0 / (((f64)q + 0.5) / 512.0); /* reciprocal r */
+ s = (s32)(256.0 * r + 0.5); /* r in units of 1/256 rounded to nearest */
+ return ((f64)s / 256.0);
+ };
+
+ if (enabled) {
+ if (statistics && params.statistics_reset_required) {
+ for (u32 i = 0; i < params.channel_count; i++) {
+ statistics->channel_compression_gain_min[i] = 1.0f;
+ statistics->channel_max_sample[i] = 0;
+ }
+ }
+
+ for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
+ for (u32 channel = 0; channel < params.channel_count; channel++) {
+ auto sample{(Common::FixedPoint<49, 15>(inputs[channel][sample_index]) /
+ Common::FixedPoint<49, 15>::one) *
+ params.input_gain};
+ auto abs_sample{sample};
+ if (sample < 0.0f) {
+ abs_sample = -sample;
+ }
+ auto coeff{abs_sample > state.samples_average[channel] ? params.attack_coeff
+ : params.release_coeff};
+ state.samples_average[channel] +=
+ ((abs_sample - state.samples_average[channel]) * coeff).to_float();
+
+ // Reciprocal estimate
+ auto new_average_sample{Common::FixedPoint<49, 15>(
+ recip_estimate(state.samples_average[channel].to_double()))};
+ if (params.processing_mode != LightLimiterInfo::ProcessingMode::Mode1) {
+ // Two Newton-Raphson steps
+ auto temp{2.0 - (state.samples_average[channel] * new_average_sample)};
+ new_average_sample = 2.0 - (state.samples_average[channel] * temp);
+ }
+
+ auto above_threshold{state.samples_average[channel] > params.threshold};
+ auto attenuation{above_threshold ? params.threshold * new_average_sample : 1.0f};
+ coeff = attenuation < state.compression_gain[channel] ? params.attack_coeff
+ : params.release_coeff;
+ state.compression_gain[channel] +=
+ (attenuation - state.compression_gain[channel]) * coeff;
+
+ auto lookahead_sample{
+ state.look_ahead_sample_buffers[channel]
+ [state.look_ahead_sample_offsets[channel]]};
+
+ state.look_ahead_sample_buffers[channel][state.look_ahead_sample_offsets[channel]] =
+ sample;
+ state.look_ahead_sample_offsets[channel] =
+ (state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min;
+
+ outputs[channel][sample_index] = static_cast<s32>(
+ std::clamp((lookahead_sample * state.compression_gain[channel] *
+ params.output_gain * Common::FixedPoint<49, 15>::one)
+ .to_long(),
+ min, max));
+
+ if (statistics) {
+ statistics->channel_max_sample[channel] =
+ std::max(statistics->channel_max_sample[channel], abs_sample.to_float());
+ statistics->channel_compression_gain_min[channel] =
+ std::min(statistics->channel_compression_gain_min[channel],
+ state.compression_gain[channel].to_float());
+ }
+ }
+ }
+ } else {
+ for (u32 i = 0; i < params.channel_count; i++) {
+ if (params.inputs[i] != params.outputs[i]) {
+ std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
+ }
+ }
+ }
+}
+
+void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("LightLimiterVersion1Command\n\tinputs: ");
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
+ std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
+ std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+
+ for (u32 i = 0; i < parameter.channel_count; i++) {
+ input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count);
+ output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count);
+ }
+
+ auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
+
+ if (effect_enabled) {
+ if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
+ UpdateLightLimiterEffectParameter(parameter, *state_);
+ } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
+ InitializeLightLimiterEffect(parameter, *state_, workbuffer);
+ }
+ }
+
+ LightLimiterInfo::StatisticsInternal* statistics{nullptr};
+ ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
+ processor.sample_count, statistics);
+}
+
+bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n");
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
+ std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
+ std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+
+ for (u32 i = 0; i < parameter.channel_count; i++) {
+ input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count);
+ output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count);
+ }
+
+ auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
+
+ if (effect_enabled) {
+ if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
+ UpdateLightLimiterEffectParameter(parameter, *state_);
+ } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
+ InitializeLightLimiterEffect(parameter, *state_, workbuffer);
+ }
+ }
+
+ auto statistics{reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(result_state)};
+ ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
+ processor.sample_count, statistics);
+}
+
+bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/light_limiter.h b/src/audio_core/renderer/command/effect/light_limiter.h
new file mode 100644
index 000000000..5d98272c7
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/light_limiter.h
@@ -0,0 +1,103 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/effect/light_limiter.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for limiting volume between a high and low threshold.
+ * Version 1.
+ */
+struct LightLimiterVersion1Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Input parameters
+ LightLimiterInfo::ParameterVersion2 parameter;
+ /// State, updated each call
+ CpuAddr state;
+ /// Game-supplied workbuffer (Unused)
+ CpuAddr workbuffer;
+ /// Is this effect enabled?
+ bool effect_enabled;
+};
+
+/**
+ * AudioRenderer command for limiting volume between a high and low threshold.
+ * Version 2 with output statistics.
+ */
+struct LightLimiterVersion2Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Input parameters
+ LightLimiterInfo::ParameterVersion2 parameter;
+ /// State, updated each call
+ CpuAddr state;
+ /// Game-supplied workbuffer (Unused)
+ CpuAddr workbuffer;
+ /// Optional statistics, sent back to the sysmodule
+ CpuAddr result_state;
+ /// Is this effect enabled?
+ bool effect_enabled;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp
new file mode 100644
index 000000000..b3c3ba4ba
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp
@@ -0,0 +1,45 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/biquad_filter.h"
+#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
+
+namespace AudioCore::AudioRenderer {
+
+void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format(
+ "MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n",
+ input, output, needs_init[0], needs_init[1]);
+}
+
+void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
+ if (filter_tap_count > MaxBiquadFilters) {
+ LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count);
+ filter_tap_count = MaxBiquadFilters;
+ }
+
+ auto input_buffer{
+ processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
+ auto output_buffer{
+ processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
+
+ // TODO: Fix this, currently just applies the filter to the input twice,
+ // and doesn't chain the biquads together at all.
+ for (u32 i = 0; i < filter_tap_count; i++) {
+ auto state{reinterpret_cast<VoiceState::BiquadFilterState*>(states[i])};
+ if (needs_init[i]) {
+ std::memset(state, 0, sizeof(VoiceState::BiquadFilterState));
+ }
+
+ ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state,
+ processor.sample_count);
+ }
+}
+
+bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h
new file mode 100644
index 000000000..99c2c0830
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/voice/voice_info.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for applying multiple biquads at once.
+ */
+struct MultiTapBiquadFilterCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer index
+ s16 input;
+ /// Output mix buffer index
+ s16 output;
+ /// Biquad parameters
+ std::array<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads;
+ /// Biquad states, updated each call
+ std::array<CpuAddr, MaxBiquadFilters> states;
+ /// If each biquad needs initialisation
+ std::array<bool, MaxBiquadFilters> needs_init;
+ /// Number of active biquads
+ u8 filter_tap_count;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/reverb.cpp b/src/audio_core/renderer/command/effect/reverb.cpp
new file mode 100644
index 000000000..fe2b1eb43
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/reverb.cpp
@@ -0,0 +1,440 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <numbers>
+#include <ranges>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/reverb.h"
+
+namespace AudioCore::AudioRenderer {
+
+constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = {
+ 53.9532470703125f,
+ 79.19256591796875f,
+ 116.23876953125f,
+ 170.61529541015625f,
+};
+
+constexpr std::array<f32, ReverbInfo::MaxDelayLines> DecayMaxDelayLineTimes = {
+ 7.0f,
+ 9.0f,
+ 13.0f,
+ 17.0f,
+};
+
+constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps + 1>, ReverbInfo::NumEarlyModes>
+ EarlyDelayTimes = {
+ {{0.000000f, 3.500000f, 2.799988f, 3.899963f, 2.699951f, 13.399963f, 7.899963f, 8.399963f,
+ 9.899963f, 12.000000f, 12.500000f},
+ {0.000000f, 11.799988f, 5.500000f, 11.199951f, 10.399963f, 38.099976f, 22.199951f,
+ 29.599976f, 21.199951f, 24.799988f, 40.000000f},
+ {0.000000f, 41.500000f, 20.500000f, 41.299988f, 0.000000f, 29.500000f, 33.799988f,
+ 45.199951f, 46.799988f, 0.000000f, 50.000000f},
+ {33.099976f, 43.299988f, 22.799988f, 37.899963f, 14.899963f, 35.299988f, 17.899963f,
+ 34.199951f, 0.000000f, 43.299988f, 50.000000f},
+ {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
+ 0.000000f, 0.000000f, 0.000000f}},
+};
+
+constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps>, ReverbInfo::NumEarlyModes>
+ EarlyDelayGains = {{
+ {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f,
+ 0.679993f, 0.679993f},
+ {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.679993f, 0.679993f,
+ 0.679993f, 0.679993f},
+ {0.500000f, 0.699951f, 0.699951f, 0.679993f, 0.500000f, 0.679993f, 0.679993f, 0.699951f,
+ 0.679993f, 0.000000f},
+ {0.929993f, 0.919983f, 0.869995f, 0.859985f, 0.939941f, 0.809998f, 0.799988f, 0.769958f,
+ 0.759949f, 0.649963f},
+ {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
+ 0.000000f, 0.000000f},
+ }};
+
+constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
+ FdnDelayTimes = {{
+ {53.953247f, 79.192566f, 116.238770f, 130.615295f},
+ {53.953247f, 79.192566f, 116.238770f, 170.615295f},
+ {5.000000f, 10.000000f, 5.000000f, 10.000000f},
+ {47.029968f, 71.000000f, 103.000000f, 170.000000f},
+ {53.953247f, 79.192566f, 116.238770f, 170.615295f},
+ }};
+
+constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
+ DecayDelayTimes = {{
+ {7.000000f, 9.000000f, 13.000000f, 17.000000f},
+ {7.000000f, 9.000000f, 13.000000f, 17.000000f},
+ {1.000000f, 1.000000f, 1.000000f, 1.000000f},
+ {7.000000f, 7.000000f, 13.000000f, 9.000000f},
+ {7.000000f, 9.000000f, 13.000000f, 17.000000f},
+ }};
+
+/**
+ * Update the ReverbInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ */
+static void UpdateReverbEffectParameter(const ReverbInfo::ParameterVersion2& params,
+ ReverbInfo::State& state) {
+ const auto pow_10 = [](f32 val) -> f32 {
+ return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
+ };
+ const auto cos = [](f32 degrees) -> f32 {
+ return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
+ };
+
+ static bool unk_initialized{false};
+ static Common::FixedPoint<50, 14> unk_value{};
+
+ const auto sample_rate{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
+ const auto pre_delay_time{Common::FixedPoint<50, 14>::from_base(params.pre_delay)};
+
+ for (u32 i = 0; i < ReverbInfo::MaxDelayTaps; i++) {
+ auto early_delay{
+ ((pre_delay_time + EarlyDelayTimes[params.early_mode][i]) * sample_rate).to_int()};
+ early_delay = std::min(early_delay, state.pre_delay_line.sample_count_max);
+ state.early_delay_times[i] = early_delay + 1;
+ state.early_gains[i] = Common::FixedPoint<50, 14>::from_base(params.early_gain) *
+ EarlyDelayGains[params.early_mode][i];
+ }
+
+ if (params.channel_count == 2) {
+ state.early_gains[4] * 0.5f;
+ state.early_gains[5] * 0.5f;
+ }
+
+ auto pre_time{
+ ((pre_delay_time + EarlyDelayTimes[params.early_mode][10]) * sample_rate).to_int()};
+ state.pre_delay_time = std::min(pre_time, state.pre_delay_line.sample_count_max);
+
+ if (!unk_initialized) {
+ unk_value = cos((1280.0f / sample_rate).to_float());
+ unk_initialized = true;
+ }
+
+ for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
+ const auto fdn_delay{(FdnDelayTimes[params.late_mode][i] * sample_rate).to_int()};
+ state.fdn_delay_lines[i].sample_count =
+ std::min(fdn_delay, state.fdn_delay_lines[i].sample_count_max);
+ state.fdn_delay_lines[i].buffer_end =
+ &state.fdn_delay_lines[i].buffer[state.fdn_delay_lines[i].sample_count - 1];
+
+ const auto decay_delay{(DecayDelayTimes[params.late_mode][i] * sample_rate).to_int()};
+ state.decay_delay_lines[i].sample_count =
+ std::min(decay_delay, state.decay_delay_lines[i].sample_count_max);
+ state.decay_delay_lines[i].buffer_end =
+ &state.decay_delay_lines[i].buffer[state.decay_delay_lines[i].sample_count - 1];
+
+ state.decay_delay_lines[i].decay =
+ 0.5999755859375f * (1.0f - Common::FixedPoint<50, 14>::from_base(params.colouration));
+
+ auto a{(Common::FixedPoint<50, 14>(state.fdn_delay_lines[i].sample_count_max) +
+ state.decay_delay_lines[i].sample_count_max) *
+ -3};
+ auto b{a / (Common::FixedPoint<50, 14>::from_base(params.decay_time) * sample_rate)};
+ Common::FixedPoint<50, 14> c{0.0f};
+ Common::FixedPoint<50, 14> d{0.0f};
+ auto hf_decay_ratio{Common::FixedPoint<50, 14>::from_base(params.high_freq_decay_ratio)};
+
+ if (hf_decay_ratio > 0.99493408203125f) {
+ c = 0.0f;
+ d = 1.0f;
+ } else {
+ const auto e{
+ pow_10(((((1.0f / hf_decay_ratio) - 1.0f) * 2) / 100 * (b / 10)).to_float())};
+ const auto f{1.0f - e};
+ const auto g{2.0f - (unk_value * e * 2)};
+ const auto h{std::sqrt(std::pow(g.to_float(), 2.0f) - (std::pow(f, 2.0f) * 4))};
+
+ c = (g - h) / (f * 2.0f);
+ d = 1.0f - c;
+ }
+
+ state.hf_decay_prev_gain[i] = c;
+ state.hf_decay_gain[i] = pow_10((b / 1000).to_float()) * d * 0.70709228515625f;
+ state.prev_feedback_output[i] = 0;
+ }
+}
+
+/**
+ * Initialize a new ReverbInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ * @param workbuffer - Game-supplied memory for the state. (Unused)
+ * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb begins.
+ */
+static void InitializeReverbEffect(const ReverbInfo::ParameterVersion2& params,
+ ReverbInfo::State& state, const CpuAddr workbuffer,
+ const bool long_size_pre_delay_supported) {
+ state = {};
+
+ auto delay{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
+
+ for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
+ auto fdn_delay_time{(FdnMaxDelayLineTimes[i] * delay).to_uint_floor()};
+ state.fdn_delay_lines[i].Initialize(fdn_delay_time, 1.0f);
+
+ auto decay_delay_time{(DecayMaxDelayLineTimes[i] * delay).to_uint_floor()};
+ state.decay_delay_lines[i].Initialize(decay_delay_time, 0.0f);
+ }
+
+ const auto pre_delay{long_size_pre_delay_supported ? 350.0f : 150.0f};
+ const auto pre_delay_line{(pre_delay * delay).to_uint_floor()};
+ state.pre_delay_line.Initialize(pre_delay_line, 1.0f);
+
+ const auto center_delay_time{(5 * delay).to_uint_floor()};
+ state.center_delay_line.Initialize(center_delay_time, 1.0f);
+
+ UpdateReverbEffectParameter(params, state);
+
+ for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
+ std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
+ std::ranges::fill(state.decay_delay_lines[i].buffer, 0);
+ }
+ std::ranges::fill(state.center_delay_line.buffer, 0);
+ std::ranges::fill(state.pre_delay_line.buffer, 0);
+}
+
+/**
+ * Pass-through the effect, copying input to output directly, with no reverb applied.
+ *
+ * @param inputs - Array of input mix buffers to copy.
+ * @param outputs - Array of output mix buffers to receive copy.
+ * @param channel_count - Number of channels in inputs and outputs.
+ * @param sample_count - Number of samples within each channel.
+ */
+static void ApplyReverbEffectBypass(std::span<std::span<const s32>> inputs,
+ std::span<std::span<s32>> outputs, const u32 channel_count,
+ const u32 sample_count) {
+ for (u32 i = 0; i < channel_count; i++) {
+ if (inputs[i].data() != outputs[i].data()) {
+ std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
+ }
+ }
+}
+
+/**
+ * Tick the delay lines, reading and returning their current output, and writing a new decaying
+ * sample (mix).
+ *
+ * @param decay - The decay line.
+ * @param fdn - Feedback delay network.
+ * @param mix - The new calculated sample to be written and decayed.
+ * @return The next delayed and decayed sample.
+ */
+static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine& decay,
+ ReverbInfo::ReverbDelayLine& fdn,
+ const Common::FixedPoint<50, 14> mix) {
+ const auto val{decay.Read()};
+ const auto mixed{mix - (val * decay.decay)};
+ const auto out{decay.Tick(mixed) + (mixed * decay.decay)};
+
+ fdn.Tick(out);
+ return out;
+}
+
+/**
+ * Impl. Apply a Reverb according to the current state, on the input mix buffers,
+ * saving the results to the output mix buffers.
+ *
+ * @tparam NumChannels - Number of channels to process. 1-6.
+ Inputs/outputs should have this many buffers.
+ * @param params - Input parameters to update the state.
+ * @param state - State to use, must be initialized (see InitializeReverbEffect).
+ * @param inputs - Input mix buffers to perform the reverb on.
+ * @param outputs - Output mix buffers to receive the reverbed samples.
+ * @param sample_count - Number of samples to process.
+ */
+template <size_t NumChannels>
+static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
+ std::vector<std::span<const s32>>& inputs,
+ std::vector<std::span<s32>>& outputs, const u32 sample_count) {
+ constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
+ 0, 0, 1, 1, 0, 1, 0, 0, 1, 1,
+ };
+ constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
+ 0, 0, 1, 1, 0, 1, 2, 2, 3, 3,
+ };
+ constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
+ 0, 0, 1, 1, 2, 2, 4, 4, 5, 5,
+ };
+
+ std::span<const u8> tap_indexes{};
+ if constexpr (NumChannels == 1) {
+ tap_indexes = OutTapIndexes1Ch;
+ } else if constexpr (NumChannels == 2) {
+ tap_indexes = OutTapIndexes2Ch;
+ } else if constexpr (NumChannels == 4) {
+ tap_indexes = OutTapIndexes4Ch;
+ } else if constexpr (NumChannels == 6) {
+ tap_indexes = OutTapIndexes6Ch;
+ }
+
+ for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
+ std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
+
+ for (u32 early_tap = 0; early_tap < ReverbInfo::MaxDelayTaps; early_tap++) {
+ const auto sample{state.pre_delay_line.TapOut(state.early_delay_times[early_tap]) *
+ state.early_gains[early_tap]};
+ output_samples[tap_indexes[early_tap]] += sample;
+ if constexpr (NumChannels == 6) {
+ output_samples[static_cast<u32>(Channels::LFE)] += sample;
+ }
+ }
+
+ if constexpr (NumChannels == 6) {
+ output_samples[static_cast<u32>(Channels::LFE)] *= 0.2f;
+ }
+
+ Common::FixedPoint<50, 14> input_sample{};
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ input_sample += inputs[channel][sample_index];
+ }
+
+ input_sample *= 64;
+ input_sample *= Common::FixedPoint<50, 14>::from_base(params.base_gain);
+ state.pre_delay_line.Write(input_sample);
+
+ for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
+ state.prev_feedback_output[i] =
+ state.prev_feedback_output[i] * state.hf_decay_prev_gain[i] +
+ state.fdn_delay_lines[i].Read() * state.hf_decay_gain[i];
+ }
+
+ Common::FixedPoint<50, 14> pre_delay_sample{
+ state.pre_delay_line.Read() * Common::FixedPoint<50, 14>::from_base(params.late_gain)};
+
+ std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> mix_matrix{
+ state.prev_feedback_output[2] + state.prev_feedback_output[1] + pre_delay_sample,
+ -state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
+ state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
+ state.prev_feedback_output[1] - state.prev_feedback_output[2] + pre_delay_sample,
+ };
+
+ std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> allpass_samples{};
+ for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
+ allpass_samples[i] = Axfx2AllPassTick(state.decay_delay_lines[i],
+ state.fdn_delay_lines[i], mix_matrix[i]);
+ }
+
+ const auto dry_gain{Common::FixedPoint<50, 14>::from_base(params.dry_gain)};
+ const auto wet_gain{Common::FixedPoint<50, 14>::from_base(params.wet_gain)};
+
+ if constexpr (NumChannels == 6) {
+ const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
+ allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
+ allpass_samples[3], allpass_samples[2], allpass_samples[3],
+ };
+
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ auto in_sample{inputs[channel][sample_index] * dry_gain};
+
+ Common::FixedPoint<50, 14> allpass{};
+ if (channel == static_cast<u32>(Channels::Center)) {
+ allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
+ } else {
+ allpass = allpass_outputs[channel];
+ }
+
+ auto out_sample{((output_samples[channel] + allpass) * wet_gain) / 64};
+ outputs[channel][sample_index] = (in_sample + out_sample).to_int();
+ }
+ } else {
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ auto in_sample{inputs[channel][sample_index] * dry_gain};
+ auto out_sample{((output_samples[channel] + allpass_samples[channel]) * wet_gain) /
+ 64};
+ outputs[channel][sample_index] = (in_sample + out_sample).to_int();
+ }
+ }
+ }
+}
+
+/**
+ * Apply a Reverb if enabled, according to the current state, on the input mix buffers,
+ * saving the results to the output mix buffers.
+ *
+ * @param params - Input parameters to use.
+ * @param state - State to use, must be initialized (see InitializeReverbEffect).
+ * @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
+ * @param inputs - Input mix buffers to performan the reverb on.
+ * @param outputs - Output mix buffers to receive the reverbed samples.
+ * @param sample_count - Number of samples to process.
+ */
+static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
+ const bool enabled, std::vector<std::span<const s32>>& inputs,
+ std::vector<std::span<s32>>& outputs, const u32 sample_count) {
+ if (enabled) {
+ switch (params.channel_count) {
+ case 0:
+ return;
+ case 1:
+ ApplyReverbEffect<1>(params, state, inputs, outputs, sample_count);
+ break;
+ case 2:
+ ApplyReverbEffect<2>(params, state, inputs, outputs, sample_count);
+ break;
+ case 4:
+ ApplyReverbEffect<4>(params, state, inputs, outputs, sample_count);
+ break;
+ case 6:
+ ApplyReverbEffect<6>(params, state, inputs, outputs, sample_count);
+ break;
+ default:
+ ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
+ break;
+ }
+ } else {
+ ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
+ }
+}
+
+void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format(
+ "ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled,
+ long_size_pre_delay_supported);
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
+ std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
+ std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+
+ for (u32 i = 0; i < parameter.channel_count; i++) {
+ input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count);
+ output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count);
+ }
+
+ auto state_{reinterpret_cast<ReverbInfo::State*>(state)};
+
+ if (effect_enabled) {
+ if (parameter.state == ReverbInfo::ParameterState::Updating) {
+ UpdateReverbEffectParameter(parameter, *state_);
+ } else if (parameter.state == ReverbInfo::ParameterState::Initialized) {
+ InitializeReverbEffect(parameter, *state_, workbuffer, long_size_pre_delay_supported);
+ }
+ }
+ ApplyReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
+ processor.sample_count);
+}
+
+bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/reverb.h b/src/audio_core/renderer/command/effect/reverb.h
new file mode 100644
index 000000000..328756150
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/reverb.h
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/effect/reverb.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives
+ * the results.
+ */
+struct ReverbCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Input parameters
+ ReverbInfo::ParameterVersion2 parameter;
+ /// State, updated each call
+ CpuAddr state;
+ /// Game-supplied workbuffer (Unused)
+ CpuAddr workbuffer;
+ /// Is this effect enabled?
+ bool effect_enabled;
+ /// Is a longer pre-delay time supported?
+ bool long_size_pre_delay_supported;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/icommand.h b/src/audio_core/renderer/command/icommand.h
new file mode 100644
index 000000000..f2dd41254
--- /dev/null
+++ b/src/audio_core/renderer/command/icommand.h
@@ -0,0 +1,93 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+enum class CommandId : u8 {
+ /* 0x00 */ Invalid,
+ /* 0x01 */ DataSourcePcmInt16Version1,
+ /* 0x02 */ DataSourcePcmInt16Version2,
+ /* 0x03 */ DataSourcePcmFloatVersion1,
+ /* 0x04 */ DataSourcePcmFloatVersion2,
+ /* 0x05 */ DataSourceAdpcmVersion1,
+ /* 0x06 */ DataSourceAdpcmVersion2,
+ /* 0x07 */ Volume,
+ /* 0x08 */ VolumeRamp,
+ /* 0x09 */ BiquadFilter,
+ /* 0x0A */ Mix,
+ /* 0x0B */ MixRamp,
+ /* 0x0C */ MixRampGrouped,
+ /* 0x0D */ DepopPrepare,
+ /* 0x0E */ DepopForMixBuffers,
+ /* 0x0F */ Delay,
+ /* 0x10 */ Upsample,
+ /* 0x11 */ DownMix6chTo2ch,
+ /* 0x12 */ Aux,
+ /* 0x13 */ DeviceSink,
+ /* 0x14 */ CircularBufferSink,
+ /* 0x15 */ Reverb,
+ /* 0x16 */ I3dl2Reverb,
+ /* 0x17 */ Performance,
+ /* 0x18 */ ClearMixBuffer,
+ /* 0x19 */ CopyMixBuffer,
+ /* 0x1A */ LightLimiterVersion1,
+ /* 0x1B */ LightLimiterVersion2,
+ /* 0x1C */ MultiTapBiquadFilter,
+ /* 0x1D */ Capture,
+ /* 0x1E */ Compressor,
+};
+
+constexpr u32 CommandMagic{0xCAFEBABE};
+
+/**
+ * A command, generated by the host, and processed by the ADSP's AudioRenderer.
+ */
+struct ICommand {
+ virtual ~ICommand() = default;
+
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ virtual void Process(const ADSP::CommandListProcessor& processor) = 0;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0;
+
+ /// Command magic 0xCAFEBABE
+ u32 magic{};
+ /// Command enabled
+ bool enabled{};
+ /// Type of this command (see CommandId)
+ CommandId type{};
+ /// Size of this command
+ s16 size{};
+ /// Estimated processing time for this command
+ u32 estimated_process_time{};
+ /// Node id of the voice or mix this command was generated from
+ u32 node_id{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/clear_mix.cpp b/src/audio_core/renderer/command/mix/clear_mix.cpp
new file mode 100644
index 000000000..4f649d6a8
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/clear_mix.cpp
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <string>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/clear_mix.h"
+
+namespace AudioCore::AudioRenderer {
+
+void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("ClearMixBufferCommand\n");
+}
+
+void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
+ memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes());
+}
+
+bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/clear_mix.h b/src/audio_core/renderer/command/mix/clear_mix.h
new file mode 100644
index 000000000..956ec0b65
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/clear_mix.h
@@ -0,0 +1,45 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for a clearing the mix buffers.
+ * Used at the start of each command list.
+ */
+struct ClearMixBufferCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/copy_mix.cpp b/src/audio_core/renderer/command/mix/copy_mix.cpp
new file mode 100644
index 000000000..1d49f1644
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/copy_mix.cpp
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/copy_mix.h"
+
+namespace AudioCore::AudioRenderer {
+
+void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index,
+ output_index);
+}
+
+void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+ auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
+ processor.sample_count)};
+ std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32));
+}
+
+bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/copy_mix.h b/src/audio_core/renderer/command/mix/copy_mix.h
new file mode 100644
index 000000000..a59007fb6
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/copy_mix.h
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for a copying a mix buffer from input to output.
+ */
+struct CopyMixBufferCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer index
+ s16 input_index;
+ /// Output mix buffer index
+ s16 output_index;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp
new file mode 100644
index 000000000..c2bc10061
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp
@@ -0,0 +1,64 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time
+ * according to decay.
+ *
+ * @param output - Output buffer to be depopped.
+ * @param depop_sample - Depopped sample to apply to output samples.
+ * @param decay_ - Amount to decay the depopped sample for every output sample.
+ * @param sample_count - Samples to process.
+ * @return Final decayed depop sample.
+ */
+static s32 ApplyDepopMix(std::span<s32> output, const s32 depop_sample,
+ Common::FixedPoint<49, 15>& decay_, const u32 sample_count) {
+ auto sample{std::abs(depop_sample)};
+ auto decay{decay_.to_raw()};
+
+ if (depop_sample <= 0) {
+ for (u32 i = 0; i < sample_count; i++) {
+ sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
+ output[i] -= sample;
+ }
+ return -sample;
+ } else {
+ for (u32 i = 0; i < sample_count; i++) {
+ sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
+ output[i] += sample;
+ }
+ return sample;
+ }
+}
+
+void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input,
+ count, decay.to_float());
+}
+
+void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto end_index{std::min(processor.buffer_count, input + count)};
+ std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index};
+
+ for (u32 index = input; index < end_index; index++) {
+ const auto depop_sample{depop_buff[index]};
+ if (depop_sample != 0) {
+ auto input_buffer{processor.mix_buffers.subspan(index * processor.sample_count,
+ processor.sample_count)};
+ depop_buff[index] =
+ ApplyDepopMix(input_buffer, depop_sample, decay, processor.sample_count);
+ }
+ }
+}
+
+bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h
new file mode 100644
index 000000000..e7268ff27
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for depopping a mix buffer.
+ * Adds a cumulation of previous samples to the current mix buffer with a decay.
+ */
+struct DepopForMixBuffersCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Starting input mix buffer index
+ u32 input;
+ /// Number of mix buffers to depop
+ u32 count;
+ /// Amount to decay the depop sample for each new sample
+ Common::FixedPoint<49, 15> decay;
+ /// Address of the depop buffer, holding the last sample for every mix buffer
+ CpuAddr depop_buffer;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/depop_prepare.cpp b/src/audio_core/renderer/command/mix/depop_prepare.cpp
new file mode 100644
index 000000000..69bb78ccc
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/depop_prepare.cpp
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/depop_prepare.h"
+#include "audio_core/renderer/voice/voice_state.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+
+void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("DepopPrepareCommand\n\tinputs: ");
+ for (u32 i = 0; i < buffer_count; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n";
+}
+
+void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto samples{reinterpret_cast<s32*>(previous_samples)};
+ auto buffer{reinterpret_cast<s32*>(depop_buffer)};
+
+ for (u32 i = 0; i < buffer_count; i++) {
+ if (samples[i]) {
+ buffer[inputs[i]] += samples[i];
+ samples[i] = 0;
+ }
+ }
+}
+
+bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/depop_prepare.h b/src/audio_core/renderer/command/mix/depop_prepare.h
new file mode 100644
index 000000000..a5465da9a
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/depop_prepare.h
@@ -0,0 +1,54 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for preparing depop.
+ * Adds the previusly output last samples to the depop buffer.
+ */
+struct DepopPrepareCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Depop buffer offset for each mix buffer
+ std::array<s16, MaxMixBuffers> inputs;
+ /// Pointer to the previous mix buffer samples
+ CpuAddr previous_samples;
+ /// Number of mix buffers to use
+ u32 buffer_count;
+ /// Pointer to the current depop values
+ CpuAddr depop_buffer;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix.cpp b/src/audio_core/renderer/command/mix/mix.cpp
new file mode 100644
index 000000000..8ecf9b05a
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix.cpp
@@ -0,0 +1,70 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <limits>
+#include <span>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/mix.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Mix input mix buffer into output mix buffer, with volume applied to the input.
+ *
+ * @tparam Q - Number of bits for fixed point operations.
+ * @param output - Output mix buffer.
+ * @param input - Input mix buffer.
+ * @param volume - Volume applied to the input.
+ * @param sample_count - Number of samples to process.
+ */
+template <size_t Q>
+static void ApplyMix(std::span<s32> output, std::span<const s32> input, const f32 volume_,
+ const u32 sample_count) {
+ const Common::FixedPoint<64 - Q, Q> volume{volume_};
+ for (u32 i = 0; i < sample_count; i++) {
+ output[i] = (output[i] + input[i] * volume).to_int();
+ }
+}
+
+void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("MixCommand");
+ string += fmt::format("\n\tinput {:02X}", input_index);
+ string += fmt::format("\n\toutput {:02X}", output_index);
+ string += fmt::format("\n\tvolume {:.8f}", volume);
+ string += "\n";
+}
+
+void MixCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+ auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
+ processor.sample_count)};
+
+ // If volume is 0, nothing will be added to the output, so just skip.
+ if (volume == 0.0f) {
+ return;
+ }
+
+ switch (precision) {
+ case 15:
+ ApplyMix<15>(output, input, volume, processor.sample_count);
+ break;
+
+ case 23:
+ ApplyMix<23>(output, input, volume, processor.sample_count);
+ break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
+ break;
+ }
+}
+
+bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix.h b/src/audio_core/renderer/command/mix/mix.h
new file mode 100644
index 000000000..0201cf171
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix.h
@@ -0,0 +1,54 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
+ * applied to the input.
+ */
+struct MixCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Fixed point precision
+ u8 precision;
+ /// Input mix buffer index
+ s16 input_index;
+ /// Output mix buffer index
+ s16 output_index;
+ /// Mix volume applied to the input
+ f32 volume;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp
new file mode 100644
index 000000000..d67123cd8
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix_ramp.cpp
@@ -0,0 +1,82 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/mix_ramp.h"
+#include "common/fixed_point.h"
+#include "common/logging/log.h"
+
+namespace AudioCore::AudioRenderer {
+
+template <size_t Q>
+s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
+ const f32 ramp_, const u32 sample_count) {
+ Common::FixedPoint<64 - Q, Q> volume{volume_};
+ Common::FixedPoint<64 - Q, Q> sample{0};
+
+ if (ramp_ == 0.0f) {
+ for (u32 i = 0; i < sample_count; i++) {
+ sample = input[i] * volume;
+ output[i] = (output[i] + sample).to_int();
+ }
+ } else {
+ Common::FixedPoint<64 - Q, Q> ramp{ramp_};
+ for (u32 i = 0; i < sample_count; i++) {
+ sample = input[i] * volume;
+ output[i] = (output[i] + sample).to_int();
+ volume += ramp;
+ }
+ }
+ return sample.to_int();
+}
+
+template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32);
+template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32);
+
+void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
+ const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
+ string += fmt::format("MixRampCommand");
+ string += fmt::format("\n\tinput {:02X}", input_index);
+ string += fmt::format("\n\toutput {:02X}", output_index);
+ string += fmt::format("\n\tvolume {:.8f}", volume);
+ string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
+ string += fmt::format("\n\tramp {:.8f}", ramp);
+ string += "\n";
+}
+
+void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+ auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
+ processor.sample_count)};
+ const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
+ auto prev_sample_ptr{reinterpret_cast<s32*>(previous_sample)};
+
+ // If previous volume and ramp are both 0, nothing will be added to the output, so just skip.
+ if (prev_volume == 0.0f && ramp == 0.0f) {
+ *prev_sample_ptr = 0;
+ return;
+ }
+
+ switch (precision) {
+ case 15:
+ *prev_sample_ptr =
+ ApplyMixRamp<15>(output, input, prev_volume, ramp, processor.sample_count);
+ break;
+
+ case 23:
+ *prev_sample_ptr =
+ ApplyMixRamp<23>(output, input, prev_volume, ramp, processor.sample_count);
+ break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
+ break;
+ }
+}
+
+bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.h b/src/audio_core/renderer/command/mix/mix_ramp.h
new file mode 100644
index 000000000..52f74a273
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix_ramp.h
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
+ * applied to the input, and volume ramping to smooth out the transition.
+ */
+struct MixRampCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Fixed point precision
+ u8 precision;
+ /// Input mix buffer index
+ s16 input_index;
+ /// Output mix buffer index
+ s16 output_index;
+ /// Previous mix volume
+ f32 prev_volume;
+ /// Current mix volume
+ f32 volume;
+ /// Pointer to the previous sample buffer, used for depopping
+ CpuAddr previous_sample;
+};
+
+/**
+ * Mix input mix buffer into output mix buffer, with volume applied to the input.
+ * @tparam Q - Number of bits for fixed point operations.
+ * @param output - Output mix buffer.
+ * @param input - Input mix buffer.
+ * @param volume_ - Volume applied to the input.
+ * @param ramp_ - Ramp applied to volume every sample.
+ * @param sample_count - Number of samples to process.
+ * @return The final gained input sample, used for depopping.
+ */
+template <size_t Q>
+s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_,
+ u32 sample_count);
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp
new file mode 100644
index 000000000..43dbef9fc
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp
@@ -0,0 +1,65 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/mix_ramp.h"
+#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
+
+namespace AudioCore::AudioRenderer {
+
+void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
+ string += "MixRampGroupedCommand";
+ for (u32 i = 0; i < buffer_count; i++) {
+ string += fmt::format("\n\t{}", i);
+ const auto ramp{(volumes[i] - prev_volumes[i]) / static_cast<f32>(processor.sample_count)};
+ string += fmt::format("\n\t\tinput {:02X}", inputs[i]);
+ string += fmt::format("\n\t\toutput {:02X}", outputs[i]);
+ string += fmt::format("\n\t\tvolume {:.8f}", volumes[i]);
+ string += fmt::format("\n\t\tprev_volume {:.8f}", prev_volumes[i]);
+ string += fmt::format("\n\t\tramp {:.8f}", ramp);
+ string += "\n";
+ }
+}
+
+void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) {
+ std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers};
+
+ for (u32 i = 0; i < buffer_count; i++) {
+ auto last_sample{0};
+ if (prev_volumes[i] != 0.0f || volumes[i] != 0.0f) {
+ const auto output{processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count)};
+ const auto input{processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count)};
+ const auto ramp{(volumes[i] - prev_volumes[i]) /
+ static_cast<f32>(processor.sample_count)};
+
+ if (prev_volumes[i] == 0.0f && ramp == 0.0f) {
+ prev_samples[i] = 0;
+ continue;
+ }
+
+ switch (precision) {
+ case 15:
+ last_sample =
+ ApplyMixRamp<15>(output, input, prev_volumes[i], ramp, processor.sample_count);
+ break;
+ case 23:
+ last_sample =
+ ApplyMixRamp<23>(output, input, prev_volumes[i], ramp, processor.sample_count);
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
+ break;
+ }
+ }
+
+ prev_samples[i] = last_sample;
+ }
+}
+
+bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
new file mode 100644
index 000000000..3b0ce67ef
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with
+ * a volume applied to the input, and volume ramping to smooth out the transition.
+ */
+struct MixRampGroupedCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Fixed point precision
+ u8 precision;
+ /// Number of mix buffers to mix
+ u32 buffer_count;
+ /// Input mix buffer indexes for each mix buffer
+ std::array<s16, MaxMixBuffers> inputs;
+ /// Output mix buffer indexes for each mix buffer
+ std::array<s16, MaxMixBuffers> outputs;
+ /// Previous mix volumes for each mix buffer
+ std::array<f32, MaxMixBuffers> prev_volumes;
+ /// Current mix volumes for each mix buffer
+ std::array<f32, MaxMixBuffers> volumes;
+ /// Pointer to the previous sample buffer, used for depop
+ CpuAddr previous_samples;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/volume.cpp b/src/audio_core/renderer/command/mix/volume.cpp
new file mode 100644
index 000000000..b045fb062
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/volume.cpp
@@ -0,0 +1,72 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/volume.h"
+#include "common/fixed_point.h"
+#include "common/logging/log.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Apply volume to the input mix buffer, saving to the output buffer.
+ *
+ * @tparam Q - Number of bits for fixed point operations.
+ * @param output - Output mix buffer.
+ * @param input - Input mix buffer.
+ * @param volume - Volume applied to the input.
+ * @param sample_count - Number of samples to process.
+ */
+template <size_t Q>
+static void ApplyUniformGain(std::span<s32> output, std::span<const s32> input, const f32 volume,
+ const u32 sample_count) {
+ if (volume == 1.0f) {
+ std::memcpy(output.data(), input.data(), input.size_bytes());
+ } else {
+ const Common::FixedPoint<64 - Q, Q> gain{volume};
+ for (u32 i = 0; i < sample_count; i++) {
+ output[i] = (input[i] * gain).to_int();
+ }
+ }
+}
+
+void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("VolumeCommand");
+ string += fmt::format("\n\tinput {:02X}", input_index);
+ string += fmt::format("\n\toutput {:02X}", output_index);
+ string += fmt::format("\n\tvolume {:.8f}", volume);
+ string += "\n";
+}
+
+void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) {
+ // If input and output buffers are the same, and the volume is 1.0f, this won't do
+ // anything, so just skip.
+ if (input_index == output_index && volume == 1.0f) {
+ return;
+ }
+
+ auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+ auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
+ processor.sample_count)};
+
+ switch (precision) {
+ case 15:
+ ApplyUniformGain<15>(output, input, volume, processor.sample_count);
+ break;
+
+ case 23:
+ ApplyUniformGain<23>(output, input, volume, processor.sample_count);
+ break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
+ break;
+ }
+}
+
+bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/volume.h b/src/audio_core/renderer/command/mix/volume.h
new file mode 100644
index 000000000..6ae9fb794
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/volume.h
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for applying volume to a mix buffer.
+ */
+struct VolumeCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Fixed point precision
+ u8 precision;
+ /// Input mix buffer index
+ s16 input_index;
+ /// Output mix buffer index
+ s16 output_index;
+ /// Mix volume applied to the input
+ f32 volume;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/volume_ramp.cpp b/src/audio_core/renderer/command/mix/volume_ramp.cpp
new file mode 100644
index 000000000..424307148
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/volume_ramp.cpp
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/volume_ramp.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Apply volume with ramping to the input mix buffer, saving to the output buffer.
+ *
+ * @tparam Q - Number of bits for fixed point operations.
+ * @param output - Output mix buffers.
+ * @param input - Input mix buffers.
+ * @param volume - Volume applied to the input.
+ * @param ramp - Ramp applied to volume every sample.
+ * @param sample_count - Number of samples to process.
+ */
+template <size_t Q>
+static void ApplyLinearEnvelopeGain(std::span<s32> output, std::span<const s32> input,
+ const f32 volume, const f32 ramp_, const u32 sample_count) {
+ if (volume == 0.0f && ramp_ == 0.0f) {
+ std::memset(output.data(), 0, output.size_bytes());
+ } else if (volume == 1.0f && ramp_ == 0.0f) {
+ std::memcpy(output.data(), input.data(), output.size_bytes());
+ } else if (ramp_ == 0.0f) {
+ const Common::FixedPoint<64 - Q, Q> gain{volume};
+ for (u32 i = 0; i < sample_count; i++) {
+ output[i] = (input[i] * gain).to_int();
+ }
+ } else {
+ Common::FixedPoint<64 - Q, Q> gain{volume};
+ const Common::FixedPoint<64 - Q, Q> ramp{ramp_};
+ for (u32 i = 0; i < sample_count; i++) {
+ output[i] = (input[i] * gain).to_int();
+ gain += ramp;
+ }
+ }
+}
+
+void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
+ const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
+ string += fmt::format("VolumeRampCommand");
+ string += fmt::format("\n\tinput {:02X}", input_index);
+ string += fmt::format("\n\toutput {:02X}", output_index);
+ string += fmt::format("\n\tvolume {:.8f}", volume);
+ string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
+ string += fmt::format("\n\tramp {:.8f}", ramp);
+ string += "\n";
+}
+
+void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+ auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
+ processor.sample_count)};
+ const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
+
+ // If input and output buffers are the same, and the volume is 1.0f, and there's no ramping,
+ // this won't do anything, so just skip.
+ if (input_index == output_index && prev_volume == 1.0f && ramp == 0.0f) {
+ return;
+ }
+
+ switch (precision) {
+ case 15:
+ ApplyLinearEnvelopeGain<15>(output, input, prev_volume, ramp, processor.sample_count);
+ break;
+
+ case 23:
+ ApplyLinearEnvelopeGain<23>(output, input, prev_volume, ramp, processor.sample_count);
+ break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
+ break;
+ }
+}
+
+bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/volume_ramp.h b/src/audio_core/renderer/command/mix/volume_ramp.h
new file mode 100644
index 000000000..77b61547e
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/volume_ramp.h
@@ -0,0 +1,56 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth
+ * out the transition.
+ */
+struct VolumeRampCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Fixed point precision
+ u8 precision;
+ /// Input mix buffer index
+ s16 input_index;
+ /// Output mix buffer index
+ s16 output_index;
+ /// Previous mix volume applied to the input
+ f32 prev_volume;
+ /// Current mix volume applied to the input
+ f32 volume;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/performance/performance.cpp b/src/audio_core/renderer/command/performance/performance.cpp
new file mode 100644
index 000000000..985958b03
--- /dev/null
+++ b/src/audio_core/renderer/command/performance/performance.cpp
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/performance/performance.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+
+namespace AudioCore::AudioRenderer {
+
+void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state));
+}
+
+void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto base{entry_address.translated_address};
+ if (state == PerformanceState::Start) {
+ auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
+ *start_time_ptr = static_cast<u32>(
+ Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
+ processor.start_time - processor.current_processing_time)
+ .count());
+ } else if (state == PerformanceState::Stop) {
+ auto processed_time_ptr{
+ reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
+ auto entry_count_ptr{
+ reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
+
+ *processed_time_ptr = static_cast<u32>(
+ Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
+ processor.start_time - processor.current_processing_time)
+ .count());
+ (*entry_count_ptr)++;
+ }
+}
+
+bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/performance/performance.h b/src/audio_core/renderer/command/performance/performance.h
new file mode 100644
index 000000000..11a7d6c08
--- /dev/null
+++ b/src/audio_core/renderer/command/performance/performance.h
@@ -0,0 +1,51 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/performance/performance_entry_addresses.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule.
+ */
+struct PerformanceCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// State of the performance
+ PerformanceState state;
+ /// Pointers to be written
+ PerformanceEntryAddresses entry_address;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp
new file mode 100644
index 000000000..1fd90308a
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
+
+namespace AudioCore::AudioRenderer {
+
+void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("DownMix6chTo2chCommand\n\tinputs: ");
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto in_front_left{
+ processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)};
+ auto in_front_right{
+ processor.mix_buffers.subspan(inputs[1] * processor.sample_count, processor.sample_count)};
+ auto in_center{
+ processor.mix_buffers.subspan(inputs[2] * processor.sample_count, processor.sample_count)};
+ auto in_lfe{
+ processor.mix_buffers.subspan(inputs[3] * processor.sample_count, processor.sample_count)};
+ auto in_back_left{
+ processor.mix_buffers.subspan(inputs[4] * processor.sample_count, processor.sample_count)};
+ auto in_back_right{
+ processor.mix_buffers.subspan(inputs[5] * processor.sample_count, processor.sample_count)};
+
+ auto out_front_left{
+ processor.mix_buffers.subspan(outputs[0] * processor.sample_count, processor.sample_count)};
+ auto out_front_right{
+ processor.mix_buffers.subspan(outputs[1] * processor.sample_count, processor.sample_count)};
+ auto out_center{
+ processor.mix_buffers.subspan(outputs[2] * processor.sample_count, processor.sample_count)};
+ auto out_lfe{
+ processor.mix_buffers.subspan(outputs[3] * processor.sample_count, processor.sample_count)};
+ auto out_back_left{
+ processor.mix_buffers.subspan(outputs[4] * processor.sample_count, processor.sample_count)};
+ auto out_back_right{
+ processor.mix_buffers.subspan(outputs[5] * processor.sample_count, processor.sample_count)};
+
+ for (u32 i = 0; i < processor.sample_count; i++) {
+ const auto left_sample{(in_front_left[i] * down_mix_coeff[0] +
+ in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
+ in_back_left[i] * down_mix_coeff[3])
+ .to_int()};
+
+ const auto right_sample{(in_front_right[i] * down_mix_coeff[0] +
+ in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
+ in_back_right[i] * down_mix_coeff[3])
+ .to_int()};
+
+ out_front_left[i] = left_sample;
+ out_front_right[i] = right_sample;
+ }
+
+ std::memset(out_center.data(), 0, out_center.size_bytes());
+ std::memset(out_lfe.data(), 0, out_lfe.size_bytes());
+ std::memset(out_back_left.data(), 0, out_back_left.size_bytes());
+ std::memset(out_back_right.data(), 0, out_back_right.size_bytes());
+}
+
+bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h
new file mode 100644
index 000000000..dc133a73b
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for downmixing 6 channels to 2.
+ * Channel layout (SMPTE):
+ * 0 - front left
+ * 1 - front right
+ * 2 - center
+ * 3 - lfe
+ * 4 - back left
+ * 5 - back right
+ */
+struct DownMix6chTo2chCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Coefficients used for downmixing
+ std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/resample.cpp b/src/audio_core/renderer/command/resample/resample.cpp
new file mode 100644
index 000000000..070c9d2b8
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/resample.cpp
@@ -0,0 +1,883 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/command/resample/resample.h"
+
+namespace AudioCore::AudioRenderer {
+
+static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input,
+ const Common::FixedPoint<49, 15>& sample_rate_ratio,
+ Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
+ if (sample_rate_ratio == 1.0f) {
+ for (u32 i = 0; i < samples_to_write; i++) {
+ output[i] = input[i];
+ }
+ } else {
+ u32 read_index{0};
+ for (u32 i = 0; i < samples_to_write; i++) {
+ output[i] = input[read_index + (fraction >= 0.5f)];
+ fraction += sample_rate_ratio;
+ read_index += static_cast<u32>(fraction.to_int_floor());
+ fraction.clear_int();
+ }
+ }
+}
+
+static void ResampleNormalQuality(std::span<s32> output, std::span<const s16> input,
+ const Common::FixedPoint<49, 15>& sample_rate_ratio,
+ Common::FixedPoint<49, 15>& fraction,
+ const u32 samples_to_write) {
+ static constexpr std::array<f32, 512> lut0 = {
+ 0.20141602f, 0.59283447f, 0.20513916f, 0.00009155f, 0.19772339f, 0.59277344f, 0.20889282f,
+ 0.00027466f, 0.19406128f, 0.59262085f, 0.21264648f, 0.00045776f, 0.19039917f, 0.59240723f,
+ 0.21646118f, 0.00067139f, 0.18679810f, 0.59213257f, 0.22030640f, 0.00085449f, 0.18322754f,
+ 0.59176636f, 0.22415161f, 0.00103760f, 0.17968750f, 0.59133911f, 0.22802734f, 0.00125122f,
+ 0.17617798f, 0.59085083f, 0.23193359f, 0.00146484f, 0.17269897f, 0.59027100f, 0.23583984f,
+ 0.00167847f, 0.16925049f, 0.58963013f, 0.23977661f, 0.00189209f, 0.16583252f, 0.58892822f,
+ 0.24374390f, 0.00210571f, 0.16244507f, 0.58816528f, 0.24774170f, 0.00234985f, 0.15908813f,
+ 0.58731079f, 0.25173950f, 0.00256348f, 0.15576172f, 0.58639526f, 0.25576782f, 0.00280762f,
+ 0.15249634f, 0.58541870f, 0.25979614f, 0.00308228f, 0.14923096f, 0.58435059f, 0.26385498f,
+ 0.00332642f, 0.14602661f, 0.58325195f, 0.26794434f, 0.00360107f, 0.14285278f, 0.58206177f,
+ 0.27203369f, 0.00387573f, 0.13973999f, 0.58078003f, 0.27612305f, 0.00418091f, 0.13662720f,
+ 0.57946777f, 0.28024292f, 0.00448608f, 0.13357544f, 0.57806396f, 0.28436279f, 0.00479126f,
+ 0.13052368f, 0.57662964f, 0.28851318f, 0.00512695f, 0.12753296f, 0.57510376f, 0.29266357f,
+ 0.00546265f, 0.12460327f, 0.57351685f, 0.29681396f, 0.00579834f, 0.12167358f, 0.57183838f,
+ 0.30099487f, 0.00616455f, 0.11880493f, 0.57012939f, 0.30517578f, 0.00656128f, 0.11596680f,
+ 0.56835938f, 0.30935669f, 0.00695801f, 0.11318970f, 0.56649780f, 0.31353760f, 0.00735474f,
+ 0.11041260f, 0.56457520f, 0.31771851f, 0.00778198f, 0.10769653f, 0.56262207f, 0.32192993f,
+ 0.00823975f, 0.10501099f, 0.56057739f, 0.32614136f, 0.00869751f, 0.10238647f, 0.55847168f,
+ 0.33032227f, 0.00915527f, 0.09976196f, 0.55633545f, 0.33453369f, 0.00967407f, 0.09722900f,
+ 0.55410767f, 0.33874512f, 0.01019287f, 0.09469604f, 0.55181885f, 0.34295654f, 0.01071167f,
+ 0.09222412f, 0.54949951f, 0.34713745f, 0.01126099f, 0.08978271f, 0.54708862f, 0.35134888f,
+ 0.01184082f, 0.08737183f, 0.54464722f, 0.35552979f, 0.01245117f, 0.08499146f, 0.54214478f,
+ 0.35974121f, 0.01306152f, 0.08267212f, 0.53958130f, 0.36392212f, 0.01370239f, 0.08041382f,
+ 0.53695679f, 0.36810303f, 0.01437378f, 0.07815552f, 0.53427124f, 0.37225342f, 0.01507568f,
+ 0.07595825f, 0.53155518f, 0.37640381f, 0.01577759f, 0.07379150f, 0.52877808f, 0.38055420f,
+ 0.01651001f, 0.07165527f, 0.52593994f, 0.38470459f, 0.01727295f, 0.06958008f, 0.52307129f,
+ 0.38882446f, 0.01806641f, 0.06753540f, 0.52014160f, 0.39294434f, 0.01889038f, 0.06552124f,
+ 0.51715088f, 0.39703369f, 0.01974487f, 0.06356812f, 0.51409912f, 0.40112305f, 0.02059937f,
+ 0.06164551f, 0.51101685f, 0.40518188f, 0.02148438f, 0.05975342f, 0.50790405f, 0.40921021f,
+ 0.02243042f, 0.05789185f, 0.50473022f, 0.41323853f, 0.02337646f, 0.05609131f, 0.50152588f,
+ 0.41726685f, 0.02435303f, 0.05432129f, 0.49826050f, 0.42123413f, 0.02539062f, 0.05258179f,
+ 0.49493408f, 0.42520142f, 0.02642822f, 0.05087280f, 0.49160767f, 0.42913818f, 0.02749634f,
+ 0.04922485f, 0.48822021f, 0.43307495f, 0.02859497f, 0.04760742f, 0.48477173f, 0.43695068f,
+ 0.02975464f, 0.04602051f, 0.48132324f, 0.44082642f, 0.03091431f, 0.04446411f, 0.47781372f,
+ 0.44467163f, 0.03210449f, 0.04293823f, 0.47424316f, 0.44845581f, 0.03335571f, 0.04147339f,
+ 0.47067261f, 0.45223999f, 0.03460693f, 0.04003906f, 0.46704102f, 0.45599365f, 0.03591919f,
+ 0.03863525f, 0.46340942f, 0.45971680f, 0.03726196f, 0.03726196f, 0.45971680f, 0.46340942f,
+ 0.03863525f, 0.03591919f, 0.45599365f, 0.46704102f, 0.04003906f, 0.03460693f, 0.45223999f,
+ 0.47067261f, 0.04147339f, 0.03335571f, 0.44845581f, 0.47424316f, 0.04293823f, 0.03210449f,
+ 0.44467163f, 0.47781372f, 0.04446411f, 0.03091431f, 0.44082642f, 0.48132324f, 0.04602051f,
+ 0.02975464f, 0.43695068f, 0.48477173f, 0.04760742f, 0.02859497f, 0.43307495f, 0.48822021f,
+ 0.04922485f, 0.02749634f, 0.42913818f, 0.49160767f, 0.05087280f, 0.02642822f, 0.42520142f,
+ 0.49493408f, 0.05258179f, 0.02539062f, 0.42123413f, 0.49826050f, 0.05432129f, 0.02435303f,
+ 0.41726685f, 0.50152588f, 0.05609131f, 0.02337646f, 0.41323853f, 0.50473022f, 0.05789185f,
+ 0.02243042f, 0.40921021f, 0.50790405f, 0.05975342f, 0.02148438f, 0.40518188f, 0.51101685f,
+ 0.06164551f, 0.02059937f, 0.40112305f, 0.51409912f, 0.06356812f, 0.01974487f, 0.39703369f,
+ 0.51715088f, 0.06552124f, 0.01889038f, 0.39294434f, 0.52014160f, 0.06753540f, 0.01806641f,
+ 0.38882446f, 0.52307129f, 0.06958008f, 0.01727295f, 0.38470459f, 0.52593994f, 0.07165527f,
+ 0.01651001f, 0.38055420f, 0.52877808f, 0.07379150f, 0.01577759f, 0.37640381f, 0.53155518f,
+ 0.07595825f, 0.01507568f, 0.37225342f, 0.53427124f, 0.07815552f, 0.01437378f, 0.36810303f,
+ 0.53695679f, 0.08041382f, 0.01370239f, 0.36392212f, 0.53958130f, 0.08267212f, 0.01306152f,
+ 0.35974121f, 0.54214478f, 0.08499146f, 0.01245117f, 0.35552979f, 0.54464722f, 0.08737183f,
+ 0.01184082f, 0.35134888f, 0.54708862f, 0.08978271f, 0.01126099f, 0.34713745f, 0.54949951f,
+ 0.09222412f, 0.01071167f, 0.34295654f, 0.55181885f, 0.09469604f, 0.01019287f, 0.33874512f,
+ 0.55410767f, 0.09722900f, 0.00967407f, 0.33453369f, 0.55633545f, 0.09976196f, 0.00915527f,
+ 0.33032227f, 0.55847168f, 0.10238647f, 0.00869751f, 0.32614136f, 0.56057739f, 0.10501099f,
+ 0.00823975f, 0.32192993f, 0.56262207f, 0.10769653f, 0.00778198f, 0.31771851f, 0.56457520f,
+ 0.11041260f, 0.00735474f, 0.31353760f, 0.56649780f, 0.11318970f, 0.00695801f, 0.30935669f,
+ 0.56835938f, 0.11596680f, 0.00656128f, 0.30517578f, 0.57012939f, 0.11880493f, 0.00616455f,
+ 0.30099487f, 0.57183838f, 0.12167358f, 0.00579834f, 0.29681396f, 0.57351685f, 0.12460327f,
+ 0.00546265f, 0.29266357f, 0.57510376f, 0.12753296f, 0.00512695f, 0.28851318f, 0.57662964f,
+ 0.13052368f, 0.00479126f, 0.28436279f, 0.57806396f, 0.13357544f, 0.00448608f, 0.28024292f,
+ 0.57946777f, 0.13662720f, 0.00418091f, 0.27612305f, 0.58078003f, 0.13973999f, 0.00387573f,
+ 0.27203369f, 0.58206177f, 0.14285278f, 0.00360107f, 0.26794434f, 0.58325195f, 0.14602661f,
+ 0.00332642f, 0.26385498f, 0.58435059f, 0.14923096f, 0.00308228f, 0.25979614f, 0.58541870f,
+ 0.15249634f, 0.00280762f, 0.25576782f, 0.58639526f, 0.15576172f, 0.00256348f, 0.25173950f,
+ 0.58731079f, 0.15908813f, 0.00234985f, 0.24774170f, 0.58816528f, 0.16244507f, 0.00210571f,
+ 0.24374390f, 0.58892822f, 0.16583252f, 0.00189209f, 0.23977661f, 0.58963013f, 0.16925049f,
+ 0.00167847f, 0.23583984f, 0.59027100f, 0.17269897f, 0.00146484f, 0.23193359f, 0.59085083f,
+ 0.17617798f, 0.00125122f, 0.22802734f, 0.59133911f, 0.17968750f, 0.00103760f, 0.22415161f,
+ 0.59176636f, 0.18322754f, 0.00085449f, 0.22030640f, 0.59213257f, 0.18679810f, 0.00067139f,
+ 0.21646118f, 0.59240723f, 0.19039917f, 0.00045776f, 0.21264648f, 0.59262085f, 0.19406128f,
+ 0.00027466f, 0.20889282f, 0.59277344f, 0.19772339f, 0.00009155f, 0.20513916f, 0.59283447f,
+ 0.20141602f,
+ };
+
+ static constexpr std::array<f32, 512> lut1 = {
+ 0.00207520f, 0.99606323f, 0.00210571f, -0.00015259f, -0.00610352f, 0.99578857f,
+ 0.00646973f, -0.00045776f, -0.01000977f, 0.99526978f, 0.01095581f, -0.00079346f,
+ -0.01373291f, 0.99444580f, 0.01562500f, -0.00109863f, -0.01733398f, 0.99337769f,
+ 0.02041626f, -0.00143433f, -0.02075195f, 0.99203491f, 0.02539062f, -0.00177002f,
+ -0.02404785f, 0.99041748f, 0.03051758f, -0.00210571f, -0.02719116f, 0.98855591f,
+ 0.03582764f, -0.00244141f, -0.03021240f, 0.98641968f, 0.04125977f, -0.00280762f,
+ -0.03308105f, 0.98400879f, 0.04687500f, -0.00314331f, -0.03579712f, 0.98135376f,
+ 0.05261230f, -0.00350952f, -0.03839111f, 0.97842407f, 0.05856323f, -0.00390625f,
+ -0.04083252f, 0.97521973f, 0.06463623f, -0.00427246f, -0.04315186f, 0.97180176f,
+ 0.07086182f, -0.00466919f, -0.04534912f, 0.96810913f, 0.07727051f, -0.00509644f,
+ -0.04742432f, 0.96414185f, 0.08383179f, -0.00549316f, -0.04934692f, 0.95996094f,
+ 0.09054565f, -0.00592041f, -0.05114746f, 0.95550537f, 0.09741211f, -0.00637817f,
+ -0.05285645f, 0.95083618f, 0.10443115f, -0.00683594f, -0.05441284f, 0.94589233f,
+ 0.11160278f, -0.00732422f, -0.05584717f, 0.94073486f, 0.11892700f, -0.00781250f,
+ -0.05718994f, 0.93533325f, 0.12643433f, -0.00830078f, -0.05841064f, 0.92968750f,
+ 0.13406372f, -0.00881958f, -0.05953979f, 0.92382812f, 0.14184570f, -0.00936890f,
+ -0.06054688f, 0.91772461f, 0.14978027f, -0.00991821f, -0.06146240f, 0.91143799f,
+ 0.15783691f, -0.01046753f, -0.06225586f, 0.90490723f, 0.16607666f, -0.01104736f,
+ -0.06295776f, 0.89816284f, 0.17443848f, -0.01165771f, -0.06356812f, 0.89120483f,
+ 0.18292236f, -0.01229858f, -0.06408691f, 0.88403320f, 0.19155884f, -0.01293945f,
+ -0.06451416f, 0.87667847f, 0.20034790f, -0.01358032f, -0.06484985f, 0.86914062f,
+ 0.20925903f, -0.01428223f, -0.06509399f, 0.86138916f, 0.21829224f, -0.01495361f,
+ -0.06527710f, 0.85345459f, 0.22744751f, -0.01568604f, -0.06536865f, 0.84533691f,
+ 0.23675537f, -0.01641846f, -0.06536865f, 0.83703613f, 0.24615479f, -0.01718140f,
+ -0.06533813f, 0.82858276f, 0.25567627f, -0.01794434f, -0.06518555f, 0.81991577f,
+ 0.26531982f, -0.01873779f, -0.06500244f, 0.81112671f, 0.27505493f, -0.01956177f,
+ -0.06472778f, 0.80215454f, 0.28491211f, -0.02038574f, -0.06442261f, 0.79306030f,
+ 0.29489136f, -0.02124023f, -0.06402588f, 0.78378296f, 0.30496216f, -0.02209473f,
+ -0.06359863f, 0.77438354f, 0.31512451f, -0.02297974f, -0.06307983f, 0.76486206f,
+ 0.32537842f, -0.02389526f, -0.06253052f, 0.75518799f, 0.33569336f, -0.02481079f,
+ -0.06195068f, 0.74539185f, 0.34613037f, -0.02575684f, -0.06130981f, 0.73547363f,
+ 0.35662842f, -0.02670288f, -0.06060791f, 0.72543335f, 0.36721802f, -0.02767944f,
+ -0.05987549f, 0.71527100f, 0.37786865f, -0.02865601f, -0.05911255f, 0.70504761f,
+ 0.38858032f, -0.02966309f, -0.05831909f, 0.69470215f, 0.39935303f, -0.03067017f,
+ -0.05746460f, 0.68426514f, 0.41018677f, -0.03170776f, -0.05661011f, 0.67373657f,
+ 0.42108154f, -0.03271484f, -0.05569458f, 0.66311646f, 0.43200684f, -0.03378296f,
+ -0.05477905f, 0.65246582f, 0.44299316f, -0.03482056f, -0.05383301f, 0.64169312f,
+ 0.45401001f, -0.03588867f, -0.05285645f, 0.63088989f, 0.46505737f, -0.03695679f,
+ -0.05187988f, 0.62002563f, 0.47613525f, -0.03802490f, -0.05087280f, 0.60910034f,
+ 0.48721313f, -0.03912354f, -0.04983521f, 0.59814453f, 0.49832153f, -0.04019165f,
+ -0.04879761f, 0.58712769f, 0.50946045f, -0.04129028f, -0.04772949f, 0.57611084f,
+ 0.52056885f, -0.04235840f, -0.04669189f, 0.56503296f, 0.53170776f, -0.04345703f,
+ -0.04562378f, 0.55392456f, 0.54281616f, -0.04452515f, -0.04452515f, 0.54281616f,
+ 0.55392456f, -0.04562378f, -0.04345703f, 0.53170776f, 0.56503296f, -0.04669189f,
+ -0.04235840f, 0.52056885f, 0.57611084f, -0.04772949f, -0.04129028f, 0.50946045f,
+ 0.58712769f, -0.04879761f, -0.04019165f, 0.49832153f, 0.59814453f, -0.04983521f,
+ -0.03912354f, 0.48721313f, 0.60910034f, -0.05087280f, -0.03802490f, 0.47613525f,
+ 0.62002563f, -0.05187988f, -0.03695679f, 0.46505737f, 0.63088989f, -0.05285645f,
+ -0.03588867f, 0.45401001f, 0.64169312f, -0.05383301f, -0.03482056f, 0.44299316f,
+ 0.65246582f, -0.05477905f, -0.03378296f, 0.43200684f, 0.66311646f, -0.05569458f,
+ -0.03271484f, 0.42108154f, 0.67373657f, -0.05661011f, -0.03170776f, 0.41018677f,
+ 0.68426514f, -0.05746460f, -0.03067017f, 0.39935303f, 0.69470215f, -0.05831909f,
+ -0.02966309f, 0.38858032f, 0.70504761f, -0.05911255f, -0.02865601f, 0.37786865f,
+ 0.71527100f, -0.05987549f, -0.02767944f, 0.36721802f, 0.72543335f, -0.06060791f,
+ -0.02670288f, 0.35662842f, 0.73547363f, -0.06130981f, -0.02575684f, 0.34613037f,
+ 0.74539185f, -0.06195068f, -0.02481079f, 0.33569336f, 0.75518799f, -0.06253052f,
+ -0.02389526f, 0.32537842f, 0.76486206f, -0.06307983f, -0.02297974f, 0.31512451f,
+ 0.77438354f, -0.06359863f, -0.02209473f, 0.30496216f, 0.78378296f, -0.06402588f,
+ -0.02124023f, 0.29489136f, 0.79306030f, -0.06442261f, -0.02038574f, 0.28491211f,
+ 0.80215454f, -0.06472778f, -0.01956177f, 0.27505493f, 0.81112671f, -0.06500244f,
+ -0.01873779f, 0.26531982f, 0.81991577f, -0.06518555f, -0.01794434f, 0.25567627f,
+ 0.82858276f, -0.06533813f, -0.01718140f, 0.24615479f, 0.83703613f, -0.06536865f,
+ -0.01641846f, 0.23675537f, 0.84533691f, -0.06536865f, -0.01568604f, 0.22744751f,
+ 0.85345459f, -0.06527710f, -0.01495361f, 0.21829224f, 0.86138916f, -0.06509399f,
+ -0.01428223f, 0.20925903f, 0.86914062f, -0.06484985f, -0.01358032f, 0.20034790f,
+ 0.87667847f, -0.06451416f, -0.01293945f, 0.19155884f, 0.88403320f, -0.06408691f,
+ -0.01229858f, 0.18292236f, 0.89120483f, -0.06356812f, -0.01165771f, 0.17443848f,
+ 0.89816284f, -0.06295776f, -0.01104736f, 0.16607666f, 0.90490723f, -0.06225586f,
+ -0.01046753f, 0.15783691f, 0.91143799f, -0.06146240f, -0.00991821f, 0.14978027f,
+ 0.91772461f, -0.06054688f, -0.00936890f, 0.14184570f, 0.92382812f, -0.05953979f,
+ -0.00881958f, 0.13406372f, 0.92968750f, -0.05841064f, -0.00830078f, 0.12643433f,
+ 0.93533325f, -0.05718994f, -0.00781250f, 0.11892700f, 0.94073486f, -0.05584717f,
+ -0.00732422f, 0.11160278f, 0.94589233f, -0.05441284f, -0.00683594f, 0.10443115f,
+ 0.95083618f, -0.05285645f, -0.00637817f, 0.09741211f, 0.95550537f, -0.05114746f,
+ -0.00592041f, 0.09054565f, 0.95996094f, -0.04934692f, -0.00549316f, 0.08383179f,
+ 0.96414185f, -0.04742432f, -0.00509644f, 0.07727051f, 0.96810913f, -0.04534912f,
+ -0.00466919f, 0.07086182f, 0.97180176f, -0.04315186f, -0.00427246f, 0.06463623f,
+ 0.97521973f, -0.04083252f, -0.00390625f, 0.05856323f, 0.97842407f, -0.03839111f,
+ -0.00350952f, 0.05261230f, 0.98135376f, -0.03579712f, -0.00314331f, 0.04687500f,
+ 0.98400879f, -0.03308105f, -0.00280762f, 0.04125977f, 0.98641968f, -0.03021240f,
+ -0.00244141f, 0.03582764f, 0.98855591f, -0.02719116f, -0.00210571f, 0.03051758f,
+ 0.99041748f, -0.02404785f, -0.00177002f, 0.02539062f, 0.99203491f, -0.02075195f,
+ -0.00143433f, 0.02041626f, 0.99337769f, -0.01733398f, -0.00109863f, 0.01562500f,
+ 0.99444580f, -0.01373291f, -0.00079346f, 0.01095581f, 0.99526978f, -0.01000977f,
+ -0.00045776f, 0.00646973f, 0.99578857f, -0.00610352f, -0.00015259f, 0.00210571f,
+ 0.99606323f, -0.00207520f,
+ };
+
+ static constexpr std::array<f32, 512> lut2 = {
+ 0.09750366f, 0.80221558f, 0.10159302f, -0.00097656f, 0.09350586f, 0.80203247f,
+ 0.10580444f, -0.00103760f, 0.08959961f, 0.80169678f, 0.11010742f, -0.00115967f,
+ 0.08578491f, 0.80117798f, 0.11447144f, -0.00128174f, 0.08203125f, 0.80047607f,
+ 0.11892700f, -0.00140381f, 0.07836914f, 0.79962158f, 0.12347412f, -0.00152588f,
+ 0.07479858f, 0.79861450f, 0.12814331f, -0.00164795f, 0.07135010f, 0.79742432f,
+ 0.13287354f, -0.00177002f, 0.06796265f, 0.79605103f, 0.13769531f, -0.00192261f,
+ 0.06469727f, 0.79452515f, 0.14260864f, -0.00204468f, 0.06149292f, 0.79284668f,
+ 0.14761353f, -0.00219727f, 0.05834961f, 0.79098511f, 0.15270996f, -0.00231934f,
+ 0.05532837f, 0.78894043f, 0.15789795f, -0.00247192f, 0.05236816f, 0.78674316f,
+ 0.16317749f, -0.00265503f, 0.04949951f, 0.78442383f, 0.16851807f, -0.00280762f,
+ 0.04672241f, 0.78189087f, 0.17398071f, -0.00299072f, 0.04400635f, 0.77920532f,
+ 0.17950439f, -0.00314331f, 0.04141235f, 0.77636719f, 0.18511963f, -0.00332642f,
+ 0.03887939f, 0.77337646f, 0.19082642f, -0.00350952f, 0.03640747f, 0.77023315f,
+ 0.19659424f, -0.00369263f, 0.03402710f, 0.76693726f, 0.20248413f, -0.00387573f,
+ 0.03173828f, 0.76348877f, 0.20843506f, -0.00405884f, 0.02951050f, 0.75985718f,
+ 0.21444702f, -0.00427246f, 0.02737427f, 0.75610352f, 0.22055054f, -0.00445557f,
+ 0.02529907f, 0.75219727f, 0.22674561f, -0.00466919f, 0.02331543f, 0.74816895f,
+ 0.23300171f, -0.00485229f, 0.02139282f, 0.74398804f, 0.23931885f, -0.00506592f,
+ 0.01956177f, 0.73965454f, 0.24572754f, -0.00531006f, 0.01779175f, 0.73519897f,
+ 0.25219727f, -0.00552368f, 0.01605225f, 0.73059082f, 0.25872803f, -0.00570679f,
+ 0.01440430f, 0.72586060f, 0.26535034f, -0.00592041f, 0.01281738f, 0.72100830f,
+ 0.27203369f, -0.00616455f, 0.01132202f, 0.71600342f, 0.27877808f, -0.00637817f,
+ 0.00988770f, 0.71090698f, 0.28558350f, -0.00656128f, 0.00851440f, 0.70565796f,
+ 0.29244995f, -0.00677490f, 0.00720215f, 0.70031738f, 0.29934692f, -0.00701904f,
+ 0.00592041f, 0.69485474f, 0.30633545f, -0.00723267f, 0.00469971f, 0.68927002f,
+ 0.31338501f, -0.00741577f, 0.00357056f, 0.68356323f, 0.32046509f, -0.00762939f,
+ 0.00247192f, 0.67773438f, 0.32760620f, -0.00787354f, 0.00143433f, 0.67184448f,
+ 0.33477783f, -0.00808716f, 0.00045776f, 0.66583252f, 0.34197998f, -0.00827026f,
+ -0.00048828f, 0.65972900f, 0.34924316f, -0.00845337f, -0.00134277f, 0.65353394f,
+ 0.35656738f, -0.00863647f, -0.00216675f, 0.64721680f, 0.36389160f, -0.00885010f,
+ -0.00296021f, 0.64083862f, 0.37127686f, -0.00903320f, -0.00369263f, 0.63433838f,
+ 0.37869263f, -0.00921631f, -0.00436401f, 0.62777710f, 0.38613892f, -0.00933838f,
+ -0.00497437f, 0.62115479f, 0.39361572f, -0.00949097f, -0.00558472f, 0.61444092f,
+ 0.40109253f, -0.00964355f, -0.00613403f, 0.60763550f, 0.40859985f, -0.00979614f,
+ -0.00665283f, 0.60076904f, 0.41610718f, -0.00991821f, -0.00714111f, 0.59384155f,
+ 0.42364502f, -0.01000977f, -0.00756836f, 0.58685303f, 0.43121338f, -0.01013184f,
+ -0.00796509f, 0.57977295f, 0.43875122f, -0.01022339f, -0.00833130f, 0.57266235f,
+ 0.44631958f, -0.01028442f, -0.00866699f, 0.56552124f, 0.45388794f, -0.01034546f,
+ -0.00897217f, 0.55831909f, 0.46145630f, -0.01040649f, -0.00921631f, 0.55105591f,
+ 0.46902466f, -0.01040649f, -0.00946045f, 0.54373169f, 0.47659302f, -0.01040649f,
+ -0.00967407f, 0.53640747f, 0.48413086f, -0.01037598f, -0.00985718f, 0.52902222f,
+ 0.49166870f, -0.01037598f, -0.01000977f, 0.52160645f, 0.49917603f, -0.01031494f,
+ -0.01013184f, 0.51416016f, 0.50668335f, -0.01025391f, -0.01025391f, 0.50668335f,
+ 0.51416016f, -0.01013184f, -0.01031494f, 0.49917603f, 0.52160645f, -0.01000977f,
+ -0.01037598f, 0.49166870f, 0.52902222f, -0.00985718f, -0.01037598f, 0.48413086f,
+ 0.53640747f, -0.00967407f, -0.01040649f, 0.47659302f, 0.54373169f, -0.00946045f,
+ -0.01040649f, 0.46902466f, 0.55105591f, -0.00921631f, -0.01040649f, 0.46145630f,
+ 0.55831909f, -0.00897217f, -0.01034546f, 0.45388794f, 0.56552124f, -0.00866699f,
+ -0.01028442f, 0.44631958f, 0.57266235f, -0.00833130f, -0.01022339f, 0.43875122f,
+ 0.57977295f, -0.00796509f, -0.01013184f, 0.43121338f, 0.58685303f, -0.00756836f,
+ -0.01000977f, 0.42364502f, 0.59384155f, -0.00714111f, -0.00991821f, 0.41610718f,
+ 0.60076904f, -0.00665283f, -0.00979614f, 0.40859985f, 0.60763550f, -0.00613403f,
+ -0.00964355f, 0.40109253f, 0.61444092f, -0.00558472f, -0.00949097f, 0.39361572f,
+ 0.62115479f, -0.00497437f, -0.00933838f, 0.38613892f, 0.62777710f, -0.00436401f,
+ -0.00921631f, 0.37869263f, 0.63433838f, -0.00369263f, -0.00903320f, 0.37127686f,
+ 0.64083862f, -0.00296021f, -0.00885010f, 0.36389160f, 0.64721680f, -0.00216675f,
+ -0.00863647f, 0.35656738f, 0.65353394f, -0.00134277f, -0.00845337f, 0.34924316f,
+ 0.65972900f, -0.00048828f, -0.00827026f, 0.34197998f, 0.66583252f, 0.00045776f,
+ -0.00808716f, 0.33477783f, 0.67184448f, 0.00143433f, -0.00787354f, 0.32760620f,
+ 0.67773438f, 0.00247192f, -0.00762939f, 0.32046509f, 0.68356323f, 0.00357056f,
+ -0.00741577f, 0.31338501f, 0.68927002f, 0.00469971f, -0.00723267f, 0.30633545f,
+ 0.69485474f, 0.00592041f, -0.00701904f, 0.29934692f, 0.70031738f, 0.00720215f,
+ -0.00677490f, 0.29244995f, 0.70565796f, 0.00851440f, -0.00656128f, 0.28558350f,
+ 0.71090698f, 0.00988770f, -0.00637817f, 0.27877808f, 0.71600342f, 0.01132202f,
+ -0.00616455f, 0.27203369f, 0.72100830f, 0.01281738f, -0.00592041f, 0.26535034f,
+ 0.72586060f, 0.01440430f, -0.00570679f, 0.25872803f, 0.73059082f, 0.01605225f,
+ -0.00552368f, 0.25219727f, 0.73519897f, 0.01779175f, -0.00531006f, 0.24572754f,
+ 0.73965454f, 0.01956177f, -0.00506592f, 0.23931885f, 0.74398804f, 0.02139282f,
+ -0.00485229f, 0.23300171f, 0.74816895f, 0.02331543f, -0.00466919f, 0.22674561f,
+ 0.75219727f, 0.02529907f, -0.00445557f, 0.22055054f, 0.75610352f, 0.02737427f,
+ -0.00427246f, 0.21444702f, 0.75985718f, 0.02951050f, -0.00405884f, 0.20843506f,
+ 0.76348877f, 0.03173828f, -0.00387573f, 0.20248413f, 0.76693726f, 0.03402710f,
+ -0.00369263f, 0.19659424f, 0.77023315f, 0.03640747f, -0.00350952f, 0.19082642f,
+ 0.77337646f, 0.03887939f, -0.00332642f, 0.18511963f, 0.77636719f, 0.04141235f,
+ -0.00314331f, 0.17950439f, 0.77920532f, 0.04400635f, -0.00299072f, 0.17398071f,
+ 0.78189087f, 0.04672241f, -0.00280762f, 0.16851807f, 0.78442383f, 0.04949951f,
+ -0.00265503f, 0.16317749f, 0.78674316f, 0.05236816f, -0.00247192f, 0.15789795f,
+ 0.78894043f, 0.05532837f, -0.00231934f, 0.15270996f, 0.79098511f, 0.05834961f,
+ -0.00219727f, 0.14761353f, 0.79284668f, 0.06149292f, -0.00204468f, 0.14260864f,
+ 0.79452515f, 0.06469727f, -0.00192261f, 0.13769531f, 0.79605103f, 0.06796265f,
+ -0.00177002f, 0.13287354f, 0.79742432f, 0.07135010f, -0.00164795f, 0.12814331f,
+ 0.79861450f, 0.07479858f, -0.00152588f, 0.12347412f, 0.79962158f, 0.07836914f,
+ -0.00140381f, 0.11892700f, 0.80047607f, 0.08203125f, -0.00128174f, 0.11447144f,
+ 0.80117798f, 0.08578491f, -0.00115967f, 0.11010742f, 0.80169678f, 0.08959961f,
+ -0.00103760f, 0.10580444f, 0.80203247f, 0.09350586f, -0.00097656f, 0.10159302f,
+ 0.80221558f, 0.09750366f,
+ };
+
+ const auto get_lut = [&]() -> std::span<const f32> {
+ if (sample_rate_ratio <= 1.0f) {
+ return std::span<const f32>(lut2.data(), lut2.size());
+ } else if (sample_rate_ratio < 1.3f) {
+ return std::span<const f32>(lut1.data(), lut1.size());
+ } else {
+ return std::span<const f32>(lut0.data(), lut0.size());
+ }
+ };
+
+ auto lut{get_lut()};
+ u32 read_index{0};
+ for (u32 i = 0; i < samples_to_write; i++) {
+ const auto lut_index{(fraction.get_frac() >> 8) * 4};
+ const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
+ const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
+ const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
+ const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]};
+ output[i] = (sample0 + sample1 + sample2 + sample3).to_int_floor();
+ fraction += sample_rate_ratio;
+ read_index += static_cast<u32>(fraction.to_int_floor());
+ fraction.clear_int();
+ }
+}
+
+static void ResampleHighQuality(std::span<s32> output, std::span<const s16> input,
+ const Common::FixedPoint<49, 15>& sample_rate_ratio,
+ Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
+ static constexpr std::array<f32, 1024> lut0 = {
+ -0.01776123f, -0.00070190f, 0.26672363f, 0.50006104f, 0.26956177f, 0.00024414f,
+ -0.01800537f, 0.00000000f, -0.01748657f, -0.00164795f, 0.26388550f, 0.50003052f,
+ 0.27236938f, 0.00122070f, -0.01824951f, -0.00003052f, -0.01724243f, -0.00256348f,
+ 0.26107788f, 0.49996948f, 0.27520752f, 0.00219727f, -0.01849365f, -0.00003052f,
+ -0.01699829f, -0.00344849f, 0.25823975f, 0.49984741f, 0.27801514f, 0.00320435f,
+ -0.01873779f, -0.00006104f, -0.01675415f, -0.00433350f, 0.25543213f, 0.49972534f,
+ 0.28085327f, 0.00424194f, -0.01898193f, -0.00006104f, -0.01651001f, -0.00518799f,
+ 0.25259399f, 0.49954224f, 0.28366089f, 0.00527954f, -0.01922607f, -0.00009155f,
+ -0.01626587f, -0.00604248f, 0.24978638f, 0.49932861f, 0.28646851f, 0.00634766f,
+ -0.01947021f, -0.00012207f, -0.01602173f, -0.00686646f, 0.24697876f, 0.49908447f,
+ 0.28930664f, 0.00744629f, -0.01971436f, -0.00015259f, -0.01574707f, -0.00765991f,
+ 0.24414062f, 0.49877930f, 0.29211426f, 0.00854492f, -0.01995850f, -0.00015259f,
+ -0.01550293f, -0.00845337f, 0.24133301f, 0.49847412f, 0.29492188f, 0.00967407f,
+ -0.02020264f, -0.00018311f, -0.01525879f, -0.00921631f, 0.23852539f, 0.49810791f,
+ 0.29772949f, 0.01083374f, -0.02044678f, -0.00021362f, -0.01501465f, -0.00997925f,
+ 0.23571777f, 0.49774170f, 0.30050659f, 0.01199341f, -0.02069092f, -0.00024414f,
+ -0.01477051f, -0.01071167f, 0.23291016f, 0.49731445f, 0.30331421f, 0.01318359f,
+ -0.02093506f, -0.00027466f, -0.01452637f, -0.01141357f, 0.23010254f, 0.49685669f,
+ 0.30609131f, 0.01437378f, -0.02117920f, -0.00030518f, -0.01428223f, -0.01211548f,
+ 0.22732544f, 0.49636841f, 0.30886841f, 0.01559448f, -0.02142334f, -0.00033569f,
+ -0.01403809f, -0.01278687f, 0.22451782f, 0.49581909f, 0.31164551f, 0.01684570f,
+ -0.02163696f, -0.00039673f, -0.01379395f, -0.01345825f, 0.22174072f, 0.49526978f,
+ 0.31442261f, 0.01809692f, -0.02188110f, -0.00042725f, -0.01358032f, -0.01409912f,
+ 0.21896362f, 0.49465942f, 0.31719971f, 0.01937866f, -0.02209473f, -0.00045776f,
+ -0.01333618f, -0.01473999f, 0.21618652f, 0.49404907f, 0.31994629f, 0.02069092f,
+ -0.02233887f, -0.00048828f, -0.01309204f, -0.01535034f, 0.21343994f, 0.49337769f,
+ 0.32269287f, 0.02203369f, -0.02255249f, -0.00054932f, -0.01284790f, -0.01596069f,
+ 0.21066284f, 0.49267578f, 0.32543945f, 0.02337646f, -0.02279663f, -0.00057983f,
+ -0.01263428f, -0.01654053f, 0.20791626f, 0.49194336f, 0.32818604f, 0.02471924f,
+ -0.02301025f, -0.00064087f, -0.01239014f, -0.01708984f, 0.20516968f, 0.49118042f,
+ 0.33090210f, 0.02612305f, -0.02322388f, -0.00067139f, -0.01214600f, -0.01763916f,
+ 0.20242310f, 0.49035645f, 0.33361816f, 0.02752686f, -0.02343750f, -0.00073242f,
+ -0.01193237f, -0.01818848f, 0.19970703f, 0.48953247f, 0.33633423f, 0.02896118f,
+ -0.02365112f, -0.00079346f, -0.01168823f, -0.01867676f, 0.19696045f, 0.48864746f,
+ 0.33901978f, 0.03039551f, -0.02386475f, -0.00082397f, -0.01147461f, -0.01919556f,
+ 0.19427490f, 0.48776245f, 0.34170532f, 0.03186035f, -0.02407837f, -0.00088501f,
+ -0.01123047f, -0.01968384f, 0.19155884f, 0.48681641f, 0.34439087f, 0.03335571f,
+ -0.02429199f, -0.00094604f, -0.01101685f, -0.02014160f, 0.18887329f, 0.48583984f,
+ 0.34704590f, 0.03485107f, -0.02447510f, -0.00100708f, -0.01080322f, -0.02059937f,
+ 0.18615723f, 0.48483276f, 0.34970093f, 0.03637695f, -0.02468872f, -0.00106812f,
+ -0.01058960f, -0.02102661f, 0.18350220f, 0.48379517f, 0.35235596f, 0.03793335f,
+ -0.02487183f, -0.00112915f, -0.01034546f, -0.02145386f, 0.18081665f, 0.48272705f,
+ 0.35498047f, 0.03948975f, -0.02505493f, -0.00119019f, -0.01013184f, -0.02188110f,
+ 0.17816162f, 0.48162842f, 0.35760498f, 0.04107666f, -0.02523804f, -0.00125122f,
+ -0.00991821f, -0.02227783f, 0.17550659f, 0.48049927f, 0.36019897f, 0.04269409f,
+ -0.02542114f, -0.00131226f, -0.00970459f, -0.02264404f, 0.17288208f, 0.47933960f,
+ 0.36279297f, 0.04431152f, -0.02560425f, -0.00140381f, -0.00952148f, -0.02301025f,
+ 0.17025757f, 0.47814941f, 0.36538696f, 0.04595947f, -0.02578735f, -0.00146484f,
+ -0.00930786f, -0.02337646f, 0.16763306f, 0.47689819f, 0.36795044f, 0.04763794f,
+ -0.02593994f, -0.00152588f, -0.00909424f, -0.02371216f, 0.16503906f, 0.47564697f,
+ 0.37048340f, 0.04931641f, -0.02609253f, -0.00161743f, -0.00888062f, -0.02401733f,
+ 0.16244507f, 0.47436523f, 0.37304688f, 0.05102539f, -0.02627563f, -0.00170898f,
+ -0.00869751f, -0.02435303f, 0.15988159f, 0.47302246f, 0.37554932f, 0.05276489f,
+ -0.02642822f, -0.00177002f, -0.00848389f, -0.02462769f, 0.15731812f, 0.47167969f,
+ 0.37805176f, 0.05450439f, -0.02658081f, -0.00186157f, -0.00830078f, -0.02493286f,
+ 0.15475464f, 0.47027588f, 0.38055420f, 0.05627441f, -0.02670288f, -0.00195312f,
+ -0.00808716f, -0.02520752f, 0.15222168f, 0.46887207f, 0.38302612f, 0.05804443f,
+ -0.02685547f, -0.00204468f, -0.00790405f, -0.02545166f, 0.14968872f, 0.46743774f,
+ 0.38546753f, 0.05987549f, -0.02697754f, -0.00213623f, -0.00772095f, -0.02569580f,
+ 0.14718628f, 0.46594238f, 0.38790894f, 0.06170654f, -0.02709961f, -0.00222778f,
+ -0.00753784f, -0.02593994f, 0.14468384f, 0.46444702f, 0.39031982f, 0.06353760f,
+ -0.02722168f, -0.00231934f, -0.00735474f, -0.02615356f, 0.14218140f, 0.46289062f,
+ 0.39273071f, 0.06539917f, -0.02734375f, -0.00241089f, -0.00717163f, -0.02636719f,
+ 0.13970947f, 0.46133423f, 0.39511108f, 0.06729126f, -0.02743530f, -0.00250244f,
+ -0.00698853f, -0.02655029f, 0.13726807f, 0.45974731f, 0.39749146f, 0.06918335f,
+ -0.02755737f, -0.00259399f, -0.00680542f, -0.02673340f, 0.13479614f, 0.45812988f,
+ 0.39984131f, 0.07113647f, -0.02764893f, -0.00271606f, -0.00662231f, -0.02691650f,
+ 0.13238525f, 0.45648193f, 0.40216064f, 0.07305908f, -0.02774048f, -0.00280762f,
+ -0.00643921f, -0.02706909f, 0.12997437f, 0.45480347f, 0.40447998f, 0.07504272f,
+ -0.02780151f, -0.00292969f, -0.00628662f, -0.02722168f, 0.12756348f, 0.45309448f,
+ 0.40676880f, 0.07699585f, -0.02789307f, -0.00305176f, -0.00610352f, -0.02734375f,
+ 0.12518311f, 0.45135498f, 0.40902710f, 0.07901001f, -0.02795410f, -0.00314331f,
+ -0.00595093f, -0.02746582f, 0.12280273f, 0.44958496f, 0.41128540f, 0.08102417f,
+ -0.02801514f, -0.00326538f, -0.00579834f, -0.02758789f, 0.12045288f, 0.44778442f,
+ 0.41351318f, 0.08306885f, -0.02804565f, -0.00338745f, -0.00561523f, -0.02770996f,
+ 0.11813354f, 0.44598389f, 0.41571045f, 0.08511353f, -0.02810669f, -0.00350952f,
+ -0.00546265f, -0.02780151f, 0.11581421f, 0.44412231f, 0.41787720f, 0.08718872f,
+ -0.02813721f, -0.00363159f, -0.00531006f, -0.02786255f, 0.11349487f, 0.44226074f,
+ 0.42004395f, 0.08929443f, -0.02816772f, -0.00375366f, -0.00515747f, -0.02795410f,
+ 0.11120605f, 0.44036865f, 0.42218018f, 0.09140015f, -0.02816772f, -0.00387573f,
+ -0.00500488f, -0.02801514f, 0.10894775f, 0.43844604f, 0.42431641f, 0.09353638f,
+ -0.02819824f, -0.00402832f, -0.00485229f, -0.02807617f, 0.10668945f, 0.43649292f,
+ 0.42639160f, 0.09570312f, -0.02819824f, -0.00415039f, -0.00469971f, -0.02810669f,
+ 0.10446167f, 0.43453979f, 0.42846680f, 0.09786987f, -0.02819824f, -0.00427246f,
+ -0.00457764f, -0.02813721f, 0.10223389f, 0.43252563f, 0.43051147f, 0.10003662f,
+ -0.02816772f, -0.00442505f, -0.00442505f, -0.02816772f, 0.10003662f, 0.43051147f,
+ 0.43252563f, 0.10223389f, -0.02813721f, -0.00457764f, -0.00427246f, -0.02819824f,
+ 0.09786987f, 0.42846680f, 0.43453979f, 0.10446167f, -0.02810669f, -0.00469971f,
+ -0.00415039f, -0.02819824f, 0.09570312f, 0.42639160f, 0.43649292f, 0.10668945f,
+ -0.02807617f, -0.00485229f, -0.00402832f, -0.02819824f, 0.09353638f, 0.42431641f,
+ 0.43844604f, 0.10894775f, -0.02801514f, -0.00500488f, -0.00387573f, -0.02816772f,
+ 0.09140015f, 0.42218018f, 0.44036865f, 0.11120605f, -0.02795410f, -0.00515747f,
+ -0.00375366f, -0.02816772f, 0.08929443f, 0.42004395f, 0.44226074f, 0.11349487f,
+ -0.02786255f, -0.00531006f, -0.00363159f, -0.02813721f, 0.08718872f, 0.41787720f,
+ 0.44412231f, 0.11581421f, -0.02780151f, -0.00546265f, -0.00350952f, -0.02810669f,
+ 0.08511353f, 0.41571045f, 0.44598389f, 0.11813354f, -0.02770996f, -0.00561523f,
+ -0.00338745f, -0.02804565f, 0.08306885f, 0.41351318f, 0.44778442f, 0.12045288f,
+ -0.02758789f, -0.00579834f, -0.00326538f, -0.02801514f, 0.08102417f, 0.41128540f,
+ 0.44958496f, 0.12280273f, -0.02746582f, -0.00595093f, -0.00314331f, -0.02795410f,
+ 0.07901001f, 0.40902710f, 0.45135498f, 0.12518311f, -0.02734375f, -0.00610352f,
+ -0.00305176f, -0.02789307f, 0.07699585f, 0.40676880f, 0.45309448f, 0.12756348f,
+ -0.02722168f, -0.00628662f, -0.00292969f, -0.02780151f, 0.07504272f, 0.40447998f,
+ 0.45480347f, 0.12997437f, -0.02706909f, -0.00643921f, -0.00280762f, -0.02774048f,
+ 0.07305908f, 0.40216064f, 0.45648193f, 0.13238525f, -0.02691650f, -0.00662231f,
+ -0.00271606f, -0.02764893f, 0.07113647f, 0.39984131f, 0.45812988f, 0.13479614f,
+ -0.02673340f, -0.00680542f, -0.00259399f, -0.02755737f, 0.06918335f, 0.39749146f,
+ 0.45974731f, 0.13726807f, -0.02655029f, -0.00698853f, -0.00250244f, -0.02743530f,
+ 0.06729126f, 0.39511108f, 0.46133423f, 0.13970947f, -0.02636719f, -0.00717163f,
+ -0.00241089f, -0.02734375f, 0.06539917f, 0.39273071f, 0.46289062f, 0.14218140f,
+ -0.02615356f, -0.00735474f, -0.00231934f, -0.02722168f, 0.06353760f, 0.39031982f,
+ 0.46444702f, 0.14468384f, -0.02593994f, -0.00753784f, -0.00222778f, -0.02709961f,
+ 0.06170654f, 0.38790894f, 0.46594238f, 0.14718628f, -0.02569580f, -0.00772095f,
+ -0.00213623f, -0.02697754f, 0.05987549f, 0.38546753f, 0.46743774f, 0.14968872f,
+ -0.02545166f, -0.00790405f, -0.00204468f, -0.02685547f, 0.05804443f, 0.38302612f,
+ 0.46887207f, 0.15222168f, -0.02520752f, -0.00808716f, -0.00195312f, -0.02670288f,
+ 0.05627441f, 0.38055420f, 0.47027588f, 0.15475464f, -0.02493286f, -0.00830078f,
+ -0.00186157f, -0.02658081f, 0.05450439f, 0.37805176f, 0.47167969f, 0.15731812f,
+ -0.02462769f, -0.00848389f, -0.00177002f, -0.02642822f, 0.05276489f, 0.37554932f,
+ 0.47302246f, 0.15988159f, -0.02435303f, -0.00869751f, -0.00170898f, -0.02627563f,
+ 0.05102539f, 0.37304688f, 0.47436523f, 0.16244507f, -0.02401733f, -0.00888062f,
+ -0.00161743f, -0.02609253f, 0.04931641f, 0.37048340f, 0.47564697f, 0.16503906f,
+ -0.02371216f, -0.00909424f, -0.00152588f, -0.02593994f, 0.04763794f, 0.36795044f,
+ 0.47689819f, 0.16763306f, -0.02337646f, -0.00930786f, -0.00146484f, -0.02578735f,
+ 0.04595947f, 0.36538696f, 0.47814941f, 0.17025757f, -0.02301025f, -0.00952148f,
+ -0.00140381f, -0.02560425f, 0.04431152f, 0.36279297f, 0.47933960f, 0.17288208f,
+ -0.02264404f, -0.00970459f, -0.00131226f, -0.02542114f, 0.04269409f, 0.36019897f,
+ 0.48049927f, 0.17550659f, -0.02227783f, -0.00991821f, -0.00125122f, -0.02523804f,
+ 0.04107666f, 0.35760498f, 0.48162842f, 0.17816162f, -0.02188110f, -0.01013184f,
+ -0.00119019f, -0.02505493f, 0.03948975f, 0.35498047f, 0.48272705f, 0.18081665f,
+ -0.02145386f, -0.01034546f, -0.00112915f, -0.02487183f, 0.03793335f, 0.35235596f,
+ 0.48379517f, 0.18350220f, -0.02102661f, -0.01058960f, -0.00106812f, -0.02468872f,
+ 0.03637695f, 0.34970093f, 0.48483276f, 0.18615723f, -0.02059937f, -0.01080322f,
+ -0.00100708f, -0.02447510f, 0.03485107f, 0.34704590f, 0.48583984f, 0.18887329f,
+ -0.02014160f, -0.01101685f, -0.00094604f, -0.02429199f, 0.03335571f, 0.34439087f,
+ 0.48681641f, 0.19155884f, -0.01968384f, -0.01123047f, -0.00088501f, -0.02407837f,
+ 0.03186035f, 0.34170532f, 0.48776245f, 0.19427490f, -0.01919556f, -0.01147461f,
+ -0.00082397f, -0.02386475f, 0.03039551f, 0.33901978f, 0.48864746f, 0.19696045f,
+ -0.01867676f, -0.01168823f, -0.00079346f, -0.02365112f, 0.02896118f, 0.33633423f,
+ 0.48953247f, 0.19970703f, -0.01818848f, -0.01193237f, -0.00073242f, -0.02343750f,
+ 0.02752686f, 0.33361816f, 0.49035645f, 0.20242310f, -0.01763916f, -0.01214600f,
+ -0.00067139f, -0.02322388f, 0.02612305f, 0.33090210f, 0.49118042f, 0.20516968f,
+ -0.01708984f, -0.01239014f, -0.00064087f, -0.02301025f, 0.02471924f, 0.32818604f,
+ 0.49194336f, 0.20791626f, -0.01654053f, -0.01263428f, -0.00057983f, -0.02279663f,
+ 0.02337646f, 0.32543945f, 0.49267578f, 0.21066284f, -0.01596069f, -0.01284790f,
+ -0.00054932f, -0.02255249f, 0.02203369f, 0.32269287f, 0.49337769f, 0.21343994f,
+ -0.01535034f, -0.01309204f, -0.00048828f, -0.02233887f, 0.02069092f, 0.31994629f,
+ 0.49404907f, 0.21618652f, -0.01473999f, -0.01333618f, -0.00045776f, -0.02209473f,
+ 0.01937866f, 0.31719971f, 0.49465942f, 0.21896362f, -0.01409912f, -0.01358032f,
+ -0.00042725f, -0.02188110f, 0.01809692f, 0.31442261f, 0.49526978f, 0.22174072f,
+ -0.01345825f, -0.01379395f, -0.00039673f, -0.02163696f, 0.01684570f, 0.31164551f,
+ 0.49581909f, 0.22451782f, -0.01278687f, -0.01403809f, -0.00033569f, -0.02142334f,
+ 0.01559448f, 0.30886841f, 0.49636841f, 0.22732544f, -0.01211548f, -0.01428223f,
+ -0.00030518f, -0.02117920f, 0.01437378f, 0.30609131f, 0.49685669f, 0.23010254f,
+ -0.01141357f, -0.01452637f, -0.00027466f, -0.02093506f, 0.01318359f, 0.30331421f,
+ 0.49731445f, 0.23291016f, -0.01071167f, -0.01477051f, -0.00024414f, -0.02069092f,
+ 0.01199341f, 0.30050659f, 0.49774170f, 0.23571777f, -0.00997925f, -0.01501465f,
+ -0.00021362f, -0.02044678f, 0.01083374f, 0.29772949f, 0.49810791f, 0.23852539f,
+ -0.00921631f, -0.01525879f, -0.00018311f, -0.02020264f, 0.00967407f, 0.29492188f,
+ 0.49847412f, 0.24133301f, -0.00845337f, -0.01550293f, -0.00015259f, -0.01995850f,
+ 0.00854492f, 0.29211426f, 0.49877930f, 0.24414062f, -0.00765991f, -0.01574707f,
+ -0.00015259f, -0.01971436f, 0.00744629f, 0.28930664f, 0.49908447f, 0.24697876f,
+ -0.00686646f, -0.01602173f, -0.00012207f, -0.01947021f, 0.00634766f, 0.28646851f,
+ 0.49932861f, 0.24978638f, -0.00604248f, -0.01626587f, -0.00009155f, -0.01922607f,
+ 0.00527954f, 0.28366089f, 0.49954224f, 0.25259399f, -0.00518799f, -0.01651001f,
+ -0.00006104f, -0.01898193f, 0.00424194f, 0.28085327f, 0.49972534f, 0.25543213f,
+ -0.00433350f, -0.01675415f, -0.00006104f, -0.01873779f, 0.00320435f, 0.27801514f,
+ 0.49984741f, 0.25823975f, -0.00344849f, -0.01699829f, -0.00003052f, -0.01849365f,
+ 0.00219727f, 0.27520752f, 0.49996948f, 0.26107788f, -0.00256348f, -0.01724243f,
+ -0.00003052f, -0.01824951f, 0.00122070f, 0.27236938f, 0.50003052f, 0.26388550f,
+ -0.00164795f, -0.01748657f, 0.00000000f, -0.01800537f, 0.00024414f, 0.26956177f,
+ 0.50006104f, 0.26672363f, -0.00070190f, -0.01776123f,
+ };
+
+ static constexpr std::array<f32, 1024> lut1 = {
+ 0.01275635f, -0.07745361f, 0.18670654f, 0.75119019f, 0.19219971f, -0.07821655f,
+ 0.01272583f, 0.00000000f, 0.01281738f, -0.07666016f, 0.18124390f, 0.75106812f,
+ 0.19772339f, -0.07897949f, 0.01266479f, 0.00003052f, 0.01284790f, -0.07583618f,
+ 0.17581177f, 0.75088501f, 0.20330811f, -0.07971191f, 0.01257324f, 0.00006104f,
+ 0.01287842f, -0.07501221f, 0.17044067f, 0.75057983f, 0.20892334f, -0.08041382f,
+ 0.01248169f, 0.00009155f, 0.01290894f, -0.07415771f, 0.16510010f, 0.75018311f,
+ 0.21453857f, -0.08111572f, 0.01239014f, 0.00012207f, 0.01290894f, -0.07330322f,
+ 0.15979004f, 0.74966431f, 0.22021484f, -0.08178711f, 0.01229858f, 0.00015259f,
+ 0.01290894f, -0.07241821f, 0.15454102f, 0.74908447f, 0.22592163f, -0.08242798f,
+ 0.01217651f, 0.00018311f, 0.01290894f, -0.07150269f, 0.14932251f, 0.74838257f,
+ 0.23165894f, -0.08303833f, 0.01205444f, 0.00021362f, 0.01290894f, -0.07058716f,
+ 0.14416504f, 0.74755859f, 0.23742676f, -0.08364868f, 0.01193237f, 0.00024414f,
+ 0.01287842f, -0.06967163f, 0.13903809f, 0.74667358f, 0.24322510f, -0.08419800f,
+ 0.01177979f, 0.00027466f, 0.01284790f, -0.06872559f, 0.13397217f, 0.74566650f,
+ 0.24905396f, -0.08474731f, 0.01162720f, 0.00033569f, 0.01281738f, -0.06777954f,
+ 0.12893677f, 0.74456787f, 0.25491333f, -0.08526611f, 0.01147461f, 0.00036621f,
+ 0.01278687f, -0.06683350f, 0.12396240f, 0.74337769f, 0.26077271f, -0.08575439f,
+ 0.01129150f, 0.00042725f, 0.01275635f, -0.06585693f, 0.11901855f, 0.74206543f,
+ 0.26669312f, -0.08621216f, 0.01110840f, 0.00045776f, 0.01269531f, -0.06488037f,
+ 0.11413574f, 0.74069214f, 0.27261353f, -0.08663940f, 0.01092529f, 0.00051880f,
+ 0.01263428f, -0.06387329f, 0.10931396f, 0.73919678f, 0.27853394f, -0.08700562f,
+ 0.01071167f, 0.00057983f, 0.01257324f, -0.06286621f, 0.10452271f, 0.73760986f,
+ 0.28451538f, -0.08737183f, 0.01049805f, 0.00064087f, 0.01251221f, -0.06185913f,
+ 0.09979248f, 0.73593140f, 0.29049683f, -0.08770752f, 0.01025391f, 0.00067139f,
+ 0.01242065f, -0.06082153f, 0.09512329f, 0.73413086f, 0.29647827f, -0.08801270f,
+ 0.01000977f, 0.00073242f, 0.01232910f, -0.05981445f, 0.09051514f, 0.73226929f,
+ 0.30249023f, -0.08828735f, 0.00973511f, 0.00079346f, 0.01226807f, -0.05877686f,
+ 0.08593750f, 0.73028564f, 0.30853271f, -0.08850098f, 0.00949097f, 0.00088501f,
+ 0.01214600f, -0.05773926f, 0.08142090f, 0.72824097f, 0.31457520f, -0.08871460f,
+ 0.00918579f, 0.00094604f, 0.01205444f, -0.05670166f, 0.07696533f, 0.72607422f,
+ 0.32061768f, -0.08886719f, 0.00891113f, 0.00100708f, 0.01196289f, -0.05563354f,
+ 0.07257080f, 0.72381592f, 0.32669067f, -0.08898926f, 0.00860596f, 0.00106812f,
+ 0.01187134f, -0.05459595f, 0.06820679f, 0.72146606f, 0.33276367f, -0.08908081f,
+ 0.00827026f, 0.00115967f, 0.01174927f, -0.05352783f, 0.06393433f, 0.71902466f,
+ 0.33883667f, -0.08911133f, 0.00796509f, 0.00122070f, 0.01162720f, -0.05245972f,
+ 0.05969238f, 0.71649170f, 0.34494019f, -0.08914185f, 0.00759888f, 0.00131226f,
+ 0.01150513f, -0.05139160f, 0.05551147f, 0.71389771f, 0.35101318f, -0.08911133f,
+ 0.00726318f, 0.00137329f, 0.01138306f, -0.05032349f, 0.05139160f, 0.71118164f,
+ 0.35711670f, -0.08901978f, 0.00686646f, 0.00146484f, 0.01126099f, -0.04928589f,
+ 0.04733276f, 0.70837402f, 0.36322021f, -0.08892822f, 0.00650024f, 0.00155640f,
+ 0.01113892f, -0.04821777f, 0.04333496f, 0.70550537f, 0.36932373f, -0.08877563f,
+ 0.00610352f, 0.00164795f, 0.01101685f, -0.04714966f, 0.03939819f, 0.70251465f,
+ 0.37542725f, -0.08856201f, 0.00567627f, 0.00173950f, 0.01086426f, -0.04608154f,
+ 0.03549194f, 0.69946289f, 0.38153076f, -0.08834839f, 0.00527954f, 0.00183105f,
+ 0.01074219f, -0.04501343f, 0.03167725f, 0.69631958f, 0.38763428f, -0.08804321f,
+ 0.00482178f, 0.00192261f, 0.01058960f, -0.04394531f, 0.02792358f, 0.69308472f,
+ 0.39370728f, -0.08773804f, 0.00436401f, 0.00201416f, 0.01043701f, -0.04287720f,
+ 0.02420044f, 0.68975830f, 0.39981079f, -0.08737183f, 0.00390625f, 0.00210571f,
+ 0.01031494f, -0.04180908f, 0.02056885f, 0.68637085f, 0.40588379f, -0.08694458f,
+ 0.00344849f, 0.00222778f, 0.01016235f, -0.04074097f, 0.01699829f, 0.68289185f,
+ 0.41195679f, -0.08648682f, 0.00296021f, 0.00231934f, 0.01000977f, -0.03970337f,
+ 0.01345825f, 0.67932129f, 0.41802979f, -0.08596802f, 0.00244141f, 0.00244141f,
+ 0.00985718f, -0.03863525f, 0.01000977f, 0.67568970f, 0.42407227f, -0.08541870f,
+ 0.00192261f, 0.00253296f, 0.00970459f, -0.03759766f, 0.00662231f, 0.67196655f,
+ 0.43011475f, -0.08480835f, 0.00140381f, 0.00265503f, 0.00955200f, -0.03652954f,
+ 0.00326538f, 0.66815186f, 0.43612671f, -0.08416748f, 0.00085449f, 0.00277710f,
+ 0.00936890f, -0.03549194f, 0.00000000f, 0.66427612f, 0.44213867f, -0.08346558f,
+ 0.00027466f, 0.00289917f, 0.00921631f, -0.03445435f, -0.00320435f, 0.66030884f,
+ 0.44812012f, -0.08270264f, -0.00027466f, 0.00299072f, 0.00906372f, -0.03344727f,
+ -0.00634766f, 0.65631104f, 0.45407104f, -0.08190918f, -0.00088501f, 0.00311279f,
+ 0.00891113f, -0.03240967f, -0.00946045f, 0.65219116f, 0.46002197f, -0.08105469f,
+ -0.00146484f, 0.00323486f, 0.00872803f, -0.03140259f, -0.01248169f, 0.64801025f,
+ 0.46594238f, -0.08013916f, -0.00210571f, 0.00338745f, 0.00857544f, -0.03039551f,
+ -0.01544189f, 0.64376831f, 0.47183228f, -0.07919312f, -0.00271606f, 0.00350952f,
+ 0.00842285f, -0.02938843f, -0.01834106f, 0.63946533f, 0.47772217f, -0.07818604f,
+ -0.00335693f, 0.00363159f, 0.00823975f, -0.02838135f, -0.02117920f, 0.63507080f,
+ 0.48358154f, -0.07711792f, -0.00402832f, 0.00375366f, 0.00808716f, -0.02740479f,
+ -0.02395630f, 0.63061523f, 0.48937988f, -0.07598877f, -0.00469971f, 0.00390625f,
+ 0.00793457f, -0.02642822f, -0.02667236f, 0.62609863f, 0.49517822f, -0.07482910f,
+ -0.00537109f, 0.00402832f, 0.00775146f, -0.02545166f, -0.02932739f, 0.62152100f,
+ 0.50094604f, -0.07357788f, -0.00607300f, 0.00418091f, 0.00759888f, -0.02450562f,
+ -0.03192139f, 0.61685181f, 0.50665283f, -0.07229614f, -0.00677490f, 0.00430298f,
+ 0.00741577f, -0.02352905f, -0.03445435f, 0.61215210f, 0.51235962f, -0.07098389f,
+ -0.00750732f, 0.00445557f, 0.00726318f, -0.02258301f, -0.03689575f, 0.60736084f,
+ 0.51800537f, -0.06958008f, -0.00823975f, 0.00460815f, 0.00711060f, -0.02166748f,
+ -0.03930664f, 0.60253906f, 0.52362061f, -0.06811523f, -0.00897217f, 0.00476074f,
+ 0.00692749f, -0.02075195f, -0.04165649f, 0.59762573f, 0.52920532f, -0.06661987f,
+ -0.00973511f, 0.00488281f, 0.00677490f, -0.01983643f, -0.04394531f, 0.59268188f,
+ 0.53475952f, -0.06506348f, -0.01052856f, 0.00503540f, 0.00662231f, -0.01892090f,
+ -0.04617310f, 0.58767700f, 0.54025269f, -0.06344604f, -0.01129150f, 0.00518799f,
+ 0.00643921f, -0.01803589f, -0.04830933f, 0.58261108f, 0.54571533f, -0.06173706f,
+ -0.01208496f, 0.00534058f, 0.00628662f, -0.01715088f, -0.05041504f, 0.57748413f,
+ 0.55111694f, -0.05999756f, -0.01290894f, 0.00549316f, 0.00613403f, -0.01626587f,
+ -0.05245972f, 0.57232666f, 0.55648804f, -0.05819702f, -0.01373291f, 0.00564575f,
+ 0.00598145f, -0.01541138f, -0.05444336f, 0.56707764f, 0.56182861f, -0.05636597f,
+ -0.01455688f, 0.00582886f, 0.00582886f, -0.01455688f, -0.05636597f, 0.56182861f,
+ 0.56707764f, -0.05444336f, -0.01541138f, 0.00598145f, 0.00564575f, -0.01373291f,
+ -0.05819702f, 0.55648804f, 0.57232666f, -0.05245972f, -0.01626587f, 0.00613403f,
+ 0.00549316f, -0.01290894f, -0.05999756f, 0.55111694f, 0.57748413f, -0.05041504f,
+ -0.01715088f, 0.00628662f, 0.00534058f, -0.01208496f, -0.06173706f, 0.54571533f,
+ 0.58261108f, -0.04830933f, -0.01803589f, 0.00643921f, 0.00518799f, -0.01129150f,
+ -0.06344604f, 0.54025269f, 0.58767700f, -0.04617310f, -0.01892090f, 0.00662231f,
+ 0.00503540f, -0.01052856f, -0.06506348f, 0.53475952f, 0.59268188f, -0.04394531f,
+ -0.01983643f, 0.00677490f, 0.00488281f, -0.00973511f, -0.06661987f, 0.52920532f,
+ 0.59762573f, -0.04165649f, -0.02075195f, 0.00692749f, 0.00476074f, -0.00897217f,
+ -0.06811523f, 0.52362061f, 0.60253906f, -0.03930664f, -0.02166748f, 0.00711060f,
+ 0.00460815f, -0.00823975f, -0.06958008f, 0.51800537f, 0.60736084f, -0.03689575f,
+ -0.02258301f, 0.00726318f, 0.00445557f, -0.00750732f, -0.07098389f, 0.51235962f,
+ 0.61215210f, -0.03445435f, -0.02352905f, 0.00741577f, 0.00430298f, -0.00677490f,
+ -0.07229614f, 0.50665283f, 0.61685181f, -0.03192139f, -0.02450562f, 0.00759888f,
+ 0.00418091f, -0.00607300f, -0.07357788f, 0.50094604f, 0.62152100f, -0.02932739f,
+ -0.02545166f, 0.00775146f, 0.00402832f, -0.00537109f, -0.07482910f, 0.49517822f,
+ 0.62609863f, -0.02667236f, -0.02642822f, 0.00793457f, 0.00390625f, -0.00469971f,
+ -0.07598877f, 0.48937988f, 0.63061523f, -0.02395630f, -0.02740479f, 0.00808716f,
+ 0.00375366f, -0.00402832f, -0.07711792f, 0.48358154f, 0.63507080f, -0.02117920f,
+ -0.02838135f, 0.00823975f, 0.00363159f, -0.00335693f, -0.07818604f, 0.47772217f,
+ 0.63946533f, -0.01834106f, -0.02938843f, 0.00842285f, 0.00350952f, -0.00271606f,
+ -0.07919312f, 0.47183228f, 0.64376831f, -0.01544189f, -0.03039551f, 0.00857544f,
+ 0.00338745f, -0.00210571f, -0.08013916f, 0.46594238f, 0.64801025f, -0.01248169f,
+ -0.03140259f, 0.00872803f, 0.00323486f, -0.00146484f, -0.08105469f, 0.46002197f,
+ 0.65219116f, -0.00946045f, -0.03240967f, 0.00891113f, 0.00311279f, -0.00088501f,
+ -0.08190918f, 0.45407104f, 0.65631104f, -0.00634766f, -0.03344727f, 0.00906372f,
+ 0.00299072f, -0.00027466f, -0.08270264f, 0.44812012f, 0.66030884f, -0.00320435f,
+ -0.03445435f, 0.00921631f, 0.00289917f, 0.00027466f, -0.08346558f, 0.44213867f,
+ 0.66427612f, 0.00000000f, -0.03549194f, 0.00936890f, 0.00277710f, 0.00085449f,
+ -0.08416748f, 0.43612671f, 0.66815186f, 0.00326538f, -0.03652954f, 0.00955200f,
+ 0.00265503f, 0.00140381f, -0.08480835f, 0.43011475f, 0.67196655f, 0.00662231f,
+ -0.03759766f, 0.00970459f, 0.00253296f, 0.00192261f, -0.08541870f, 0.42407227f,
+ 0.67568970f, 0.01000977f, -0.03863525f, 0.00985718f, 0.00244141f, 0.00244141f,
+ -0.08596802f, 0.41802979f, 0.67932129f, 0.01345825f, -0.03970337f, 0.01000977f,
+ 0.00231934f, 0.00296021f, -0.08648682f, 0.41195679f, 0.68289185f, 0.01699829f,
+ -0.04074097f, 0.01016235f, 0.00222778f, 0.00344849f, -0.08694458f, 0.40588379f,
+ 0.68637085f, 0.02056885f, -0.04180908f, 0.01031494f, 0.00210571f, 0.00390625f,
+ -0.08737183f, 0.39981079f, 0.68975830f, 0.02420044f, -0.04287720f, 0.01043701f,
+ 0.00201416f, 0.00436401f, -0.08773804f, 0.39370728f, 0.69308472f, 0.02792358f,
+ -0.04394531f, 0.01058960f, 0.00192261f, 0.00482178f, -0.08804321f, 0.38763428f,
+ 0.69631958f, 0.03167725f, -0.04501343f, 0.01074219f, 0.00183105f, 0.00527954f,
+ -0.08834839f, 0.38153076f, 0.69946289f, 0.03549194f, -0.04608154f, 0.01086426f,
+ 0.00173950f, 0.00567627f, -0.08856201f, 0.37542725f, 0.70251465f, 0.03939819f,
+ -0.04714966f, 0.01101685f, 0.00164795f, 0.00610352f, -0.08877563f, 0.36932373f,
+ 0.70550537f, 0.04333496f, -0.04821777f, 0.01113892f, 0.00155640f, 0.00650024f,
+ -0.08892822f, 0.36322021f, 0.70837402f, 0.04733276f, -0.04928589f, 0.01126099f,
+ 0.00146484f, 0.00686646f, -0.08901978f, 0.35711670f, 0.71118164f, 0.05139160f,
+ -0.05032349f, 0.01138306f, 0.00137329f, 0.00726318f, -0.08911133f, 0.35101318f,
+ 0.71389771f, 0.05551147f, -0.05139160f, 0.01150513f, 0.00131226f, 0.00759888f,
+ -0.08914185f, 0.34494019f, 0.71649170f, 0.05969238f, -0.05245972f, 0.01162720f,
+ 0.00122070f, 0.00796509f, -0.08911133f, 0.33883667f, 0.71902466f, 0.06393433f,
+ -0.05352783f, 0.01174927f, 0.00115967f, 0.00827026f, -0.08908081f, 0.33276367f,
+ 0.72146606f, 0.06820679f, -0.05459595f, 0.01187134f, 0.00106812f, 0.00860596f,
+ -0.08898926f, 0.32669067f, 0.72381592f, 0.07257080f, -0.05563354f, 0.01196289f,
+ 0.00100708f, 0.00891113f, -0.08886719f, 0.32061768f, 0.72607422f, 0.07696533f,
+ -0.05670166f, 0.01205444f, 0.00094604f, 0.00918579f, -0.08871460f, 0.31457520f,
+ 0.72824097f, 0.08142090f, -0.05773926f, 0.01214600f, 0.00088501f, 0.00949097f,
+ -0.08850098f, 0.30853271f, 0.73028564f, 0.08593750f, -0.05877686f, 0.01226807f,
+ 0.00079346f, 0.00973511f, -0.08828735f, 0.30249023f, 0.73226929f, 0.09051514f,
+ -0.05981445f, 0.01232910f, 0.00073242f, 0.01000977f, -0.08801270f, 0.29647827f,
+ 0.73413086f, 0.09512329f, -0.06082153f, 0.01242065f, 0.00067139f, 0.01025391f,
+ -0.08770752f, 0.29049683f, 0.73593140f, 0.09979248f, -0.06185913f, 0.01251221f,
+ 0.00064087f, 0.01049805f, -0.08737183f, 0.28451538f, 0.73760986f, 0.10452271f,
+ -0.06286621f, 0.01257324f, 0.00057983f, 0.01071167f, -0.08700562f, 0.27853394f,
+ 0.73919678f, 0.10931396f, -0.06387329f, 0.01263428f, 0.00051880f, 0.01092529f,
+ -0.08663940f, 0.27261353f, 0.74069214f, 0.11413574f, -0.06488037f, 0.01269531f,
+ 0.00045776f, 0.01110840f, -0.08621216f, 0.26669312f, 0.74206543f, 0.11901855f,
+ -0.06585693f, 0.01275635f, 0.00042725f, 0.01129150f, -0.08575439f, 0.26077271f,
+ 0.74337769f, 0.12396240f, -0.06683350f, 0.01278687f, 0.00036621f, 0.01147461f,
+ -0.08526611f, 0.25491333f, 0.74456787f, 0.12893677f, -0.06777954f, 0.01281738f,
+ 0.00033569f, 0.01162720f, -0.08474731f, 0.24905396f, 0.74566650f, 0.13397217f,
+ -0.06872559f, 0.01284790f, 0.00027466f, 0.01177979f, -0.08419800f, 0.24322510f,
+ 0.74667358f, 0.13903809f, -0.06967163f, 0.01287842f, 0.00024414f, 0.01193237f,
+ -0.08364868f, 0.23742676f, 0.74755859f, 0.14416504f, -0.07058716f, 0.01290894f,
+ 0.00021362f, 0.01205444f, -0.08303833f, 0.23165894f, 0.74838257f, 0.14932251f,
+ -0.07150269f, 0.01290894f, 0.00018311f, 0.01217651f, -0.08242798f, 0.22592163f,
+ 0.74908447f, 0.15454102f, -0.07241821f, 0.01290894f, 0.00015259f, 0.01229858f,
+ -0.08178711f, 0.22021484f, 0.74966431f, 0.15979004f, -0.07330322f, 0.01290894f,
+ 0.00012207f, 0.01239014f, -0.08111572f, 0.21453857f, 0.75018311f, 0.16510010f,
+ -0.07415771f, 0.01290894f, 0.00009155f, 0.01248169f, -0.08041382f, 0.20892334f,
+ 0.75057983f, 0.17044067f, -0.07501221f, 0.01287842f, 0.00006104f, 0.01257324f,
+ -0.07971191f, 0.20330811f, 0.75088501f, 0.17581177f, -0.07583618f, 0.01284790f,
+ 0.00003052f, 0.01266479f, -0.07897949f, 0.19772339f, 0.75106812f, 0.18124390f,
+ -0.07666016f, 0.01281738f, 0.00000000f, 0.01272583f, -0.07821655f, 0.19219971f,
+ 0.75119019f, 0.18670654f, -0.07745361f, 0.01275635f,
+ };
+
+ static constexpr std::array<f32, 1024> lut2 = {
+ -0.00036621f, 0.00143433f, -0.00408936f, 0.99996948f, 0.00247192f, -0.00048828f,
+ 0.00006104f, 0.00000000f, -0.00079346f, 0.00329590f, -0.01052856f, 0.99975586f,
+ 0.00918579f, -0.00241089f, 0.00051880f, -0.00003052f, -0.00122070f, 0.00512695f,
+ -0.01684570f, 0.99929810f, 0.01605225f, -0.00439453f, 0.00097656f, -0.00006104f,
+ -0.00161743f, 0.00689697f, -0.02297974f, 0.99862671f, 0.02304077f, -0.00640869f,
+ 0.00143433f, -0.00009155f, -0.00201416f, 0.00866699f, -0.02899170f, 0.99774170f,
+ 0.03018188f, -0.00845337f, 0.00192261f, -0.00015259f, -0.00238037f, 0.01037598f,
+ -0.03488159f, 0.99664307f, 0.03741455f, -0.01055908f, 0.00241089f, -0.00018311f,
+ -0.00274658f, 0.01202393f, -0.04061890f, 0.99533081f, 0.04483032f, -0.01266479f,
+ 0.00292969f, -0.00024414f, -0.00308228f, 0.01364136f, -0.04620361f, 0.99377441f,
+ 0.05233765f, -0.01483154f, 0.00344849f, -0.00027466f, -0.00341797f, 0.01522827f,
+ -0.05163574f, 0.99200439f, 0.05999756f, -0.01699829f, 0.00396729f, -0.00033569f,
+ -0.00375366f, 0.01678467f, -0.05691528f, 0.99002075f, 0.06777954f, -0.01922607f,
+ 0.00451660f, -0.00039673f, -0.00405884f, 0.01828003f, -0.06207275f, 0.98782349f,
+ 0.07568359f, -0.02145386f, 0.00506592f, -0.00042725f, -0.00436401f, 0.01971436f,
+ -0.06707764f, 0.98541260f, 0.08370972f, -0.02374268f, 0.00564575f, -0.00048828f,
+ -0.00463867f, 0.02114868f, -0.07192993f, 0.98278809f, 0.09185791f, -0.02603149f,
+ 0.00622559f, -0.00054932f, -0.00494385f, 0.02252197f, -0.07666016f, 0.97991943f,
+ 0.10012817f, -0.02835083f, 0.00680542f, -0.00061035f, -0.00518799f, 0.02383423f,
+ -0.08123779f, 0.97686768f, 0.10848999f, -0.03073120f, 0.00738525f, -0.00070190f,
+ -0.00543213f, 0.02511597f, -0.08566284f, 0.97360229f, 0.11700439f, -0.03308105f,
+ 0.00799561f, -0.00076294f, -0.00567627f, 0.02636719f, -0.08993530f, 0.97012329f,
+ 0.12561035f, -0.03549194f, 0.00860596f, -0.00082397f, -0.00592041f, 0.02755737f,
+ -0.09405518f, 0.96643066f, 0.13436890f, -0.03790283f, 0.00924683f, -0.00091553f,
+ -0.00613403f, 0.02868652f, -0.09805298f, 0.96252441f, 0.14318848f, -0.04034424f,
+ 0.00985718f, -0.00097656f, -0.00631714f, 0.02981567f, -0.10189819f, 0.95843506f,
+ 0.15213013f, -0.04281616f, 0.01049805f, -0.00106812f, -0.00653076f, 0.03085327f,
+ -0.10559082f, 0.95413208f, 0.16119385f, -0.04528809f, 0.01113892f, -0.00112915f,
+ -0.00671387f, 0.03189087f, -0.10916138f, 0.94961548f, 0.17034912f, -0.04779053f,
+ 0.01181030f, -0.00122070f, -0.00686646f, 0.03286743f, -0.11254883f, 0.94491577f,
+ 0.17959595f, -0.05029297f, 0.01248169f, -0.00131226f, -0.00701904f, 0.03378296f,
+ -0.11584473f, 0.94000244f, 0.18893433f, -0.05279541f, 0.01315308f, -0.00140381f,
+ -0.00717163f, 0.03466797f, -0.11895752f, 0.93490601f, 0.19839478f, -0.05532837f,
+ 0.01382446f, -0.00149536f, -0.00732422f, 0.03552246f, -0.12194824f, 0.92962646f,
+ 0.20791626f, -0.05786133f, 0.01449585f, -0.00158691f, -0.00744629f, 0.03631592f,
+ -0.12478638f, 0.92413330f, 0.21752930f, -0.06042480f, 0.01519775f, -0.00167847f,
+ -0.00753784f, 0.03707886f, -0.12750244f, 0.91848755f, 0.22723389f, -0.06298828f,
+ 0.01586914f, -0.00177002f, -0.00765991f, 0.03781128f, -0.13006592f, 0.91262817f,
+ 0.23703003f, -0.06555176f, 0.01657104f, -0.00189209f, -0.00775146f, 0.03848267f,
+ -0.13250732f, 0.90658569f, 0.24691772f, -0.06808472f, 0.01727295f, -0.00198364f,
+ -0.00784302f, 0.03909302f, -0.13479614f, 0.90036011f, 0.25683594f, -0.07064819f,
+ 0.01797485f, -0.00210571f, -0.00790405f, 0.03970337f, -0.13696289f, 0.89395142f,
+ 0.26687622f, -0.07321167f, 0.01870728f, -0.00219727f, -0.00796509f, 0.04025269f,
+ -0.13900757f, 0.88739014f, 0.27694702f, -0.07577515f, 0.01940918f, -0.00231934f,
+ -0.00802612f, 0.04077148f, -0.14089966f, 0.88064575f, 0.28710938f, -0.07833862f,
+ 0.02011108f, -0.00244141f, -0.00808716f, 0.04122925f, -0.14263916f, 0.87374878f,
+ 0.29733276f, -0.08090210f, 0.02084351f, -0.00253296f, -0.00811768f, 0.04165649f,
+ -0.14428711f, 0.86666870f, 0.30761719f, -0.08343506f, 0.02154541f, -0.00265503f,
+ -0.00814819f, 0.04205322f, -0.14578247f, 0.85940552f, 0.31793213f, -0.08596802f,
+ 0.02227783f, -0.00277710f, -0.00814819f, 0.04238892f, -0.14715576f, 0.85202026f,
+ 0.32833862f, -0.08847046f, 0.02297974f, -0.00289917f, -0.00817871f, 0.04272461f,
+ -0.14840698f, 0.84445190f, 0.33874512f, -0.09097290f, 0.02371216f, -0.00302124f,
+ -0.00817871f, 0.04299927f, -0.14953613f, 0.83673096f, 0.34924316f, -0.09347534f,
+ 0.02441406f, -0.00314331f, -0.00817871f, 0.04321289f, -0.15054321f, 0.82888794f,
+ 0.35977173f, -0.09594727f, 0.02514648f, -0.00326538f, -0.00814819f, 0.04342651f,
+ -0.15142822f, 0.82086182f, 0.37033081f, -0.09838867f, 0.02584839f, -0.00341797f,
+ -0.00814819f, 0.04357910f, -0.15219116f, 0.81271362f, 0.38092041f, -0.10079956f,
+ 0.02655029f, -0.00354004f, -0.00811768f, 0.04373169f, -0.15283203f, 0.80441284f,
+ 0.39154053f, -0.10321045f, 0.02725220f, -0.00366211f, -0.00808716f, 0.04382324f,
+ -0.15338135f, 0.79598999f, 0.40219116f, -0.10559082f, 0.02795410f, -0.00381470f,
+ -0.00805664f, 0.04388428f, -0.15377808f, 0.78741455f, 0.41287231f, -0.10794067f,
+ 0.02865601f, -0.00393677f, -0.00799561f, 0.04388428f, -0.15408325f, 0.77871704f,
+ 0.42358398f, -0.11026001f, 0.02935791f, -0.00405884f, -0.00793457f, 0.04388428f,
+ -0.15426636f, 0.76989746f, 0.43429565f, -0.11251831f, 0.03002930f, -0.00421143f,
+ -0.00787354f, 0.04385376f, -0.15435791f, 0.76095581f, 0.44500732f, -0.11477661f,
+ 0.03070068f, -0.00433350f, -0.00781250f, 0.04379272f, -0.15435791f, 0.75192261f,
+ 0.45574951f, -0.11697388f, 0.03137207f, -0.00448608f, -0.00775146f, 0.04367065f,
+ -0.15420532f, 0.74273682f, 0.46649170f, -0.11914062f, 0.03201294f, -0.00460815f,
+ -0.00769043f, 0.04354858f, -0.15399170f, 0.73345947f, 0.47723389f, -0.12127686f,
+ 0.03268433f, -0.00473022f, -0.00759888f, 0.04339600f, -0.15365601f, 0.72406006f,
+ 0.48794556f, -0.12335205f, 0.03329468f, -0.00488281f, -0.00750732f, 0.04321289f,
+ -0.15322876f, 0.71456909f, 0.49868774f, -0.12539673f, 0.03393555f, -0.00500488f,
+ -0.00741577f, 0.04296875f, -0.15270996f, 0.70498657f, 0.50936890f, -0.12738037f,
+ 0.03454590f, -0.00515747f, -0.00732422f, 0.04272461f, -0.15209961f, 0.69528198f,
+ 0.52008057f, -0.12930298f, 0.03515625f, -0.00527954f, -0.00723267f, 0.04248047f,
+ -0.15136719f, 0.68551636f, 0.53076172f, -0.13119507f, 0.03573608f, -0.00543213f,
+ -0.00714111f, 0.04217529f, -0.15057373f, 0.67565918f, 0.54138184f, -0.13299561f,
+ 0.03631592f, -0.00555420f, -0.00701904f, 0.04183960f, -0.14968872f, 0.66571045f,
+ 0.55200195f, -0.13476562f, 0.03689575f, -0.00567627f, -0.00692749f, 0.04150391f,
+ -0.14871216f, 0.65567017f, 0.56259155f, -0.13647461f, 0.03741455f, -0.00582886f,
+ -0.00680542f, 0.04113770f, -0.14767456f, 0.64556885f, 0.57315063f, -0.13812256f,
+ 0.03796387f, -0.00595093f, -0.00668335f, 0.04074097f, -0.14651489f, 0.63540649f,
+ 0.58364868f, -0.13970947f, 0.03845215f, -0.00607300f, -0.00656128f, 0.04031372f,
+ -0.14529419f, 0.62518311f, 0.59411621f, -0.14120483f, 0.03897095f, -0.00619507f,
+ -0.00643921f, 0.03988647f, -0.14401245f, 0.61486816f, 0.60452271f, -0.14263916f,
+ 0.03942871f, -0.00631714f, -0.00631714f, 0.03942871f, -0.14263916f, 0.60452271f,
+ 0.61486816f, -0.14401245f, 0.03988647f, -0.00643921f, -0.00619507f, 0.03897095f,
+ -0.14120483f, 0.59411621f, 0.62518311f, -0.14529419f, 0.04031372f, -0.00656128f,
+ -0.00607300f, 0.03845215f, -0.13970947f, 0.58364868f, 0.63540649f, -0.14651489f,
+ 0.04074097f, -0.00668335f, -0.00595093f, 0.03796387f, -0.13812256f, 0.57315063f,
+ 0.64556885f, -0.14767456f, 0.04113770f, -0.00680542f, -0.00582886f, 0.03741455f,
+ -0.13647461f, 0.56259155f, 0.65567017f, -0.14871216f, 0.04150391f, -0.00692749f,
+ -0.00567627f, 0.03689575f, -0.13476562f, 0.55200195f, 0.66571045f, -0.14968872f,
+ 0.04183960f, -0.00701904f, -0.00555420f, 0.03631592f, -0.13299561f, 0.54138184f,
+ 0.67565918f, -0.15057373f, 0.04217529f, -0.00714111f, -0.00543213f, 0.03573608f,
+ -0.13119507f, 0.53076172f, 0.68551636f, -0.15136719f, 0.04248047f, -0.00723267f,
+ -0.00527954f, 0.03515625f, -0.12930298f, 0.52008057f, 0.69528198f, -0.15209961f,
+ 0.04272461f, -0.00732422f, -0.00515747f, 0.03454590f, -0.12738037f, 0.50936890f,
+ 0.70498657f, -0.15270996f, 0.04296875f, -0.00741577f, -0.00500488f, 0.03393555f,
+ -0.12539673f, 0.49868774f, 0.71456909f, -0.15322876f, 0.04321289f, -0.00750732f,
+ -0.00488281f, 0.03329468f, -0.12335205f, 0.48794556f, 0.72406006f, -0.15365601f,
+ 0.04339600f, -0.00759888f, -0.00473022f, 0.03268433f, -0.12127686f, 0.47723389f,
+ 0.73345947f, -0.15399170f, 0.04354858f, -0.00769043f, -0.00460815f, 0.03201294f,
+ -0.11914062f, 0.46649170f, 0.74273682f, -0.15420532f, 0.04367065f, -0.00775146f,
+ -0.00448608f, 0.03137207f, -0.11697388f, 0.45574951f, 0.75192261f, -0.15435791f,
+ 0.04379272f, -0.00781250f, -0.00433350f, 0.03070068f, -0.11477661f, 0.44500732f,
+ 0.76095581f, -0.15435791f, 0.04385376f, -0.00787354f, -0.00421143f, 0.03002930f,
+ -0.11251831f, 0.43429565f, 0.76989746f, -0.15426636f, 0.04388428f, -0.00793457f,
+ -0.00405884f, 0.02935791f, -0.11026001f, 0.42358398f, 0.77871704f, -0.15408325f,
+ 0.04388428f, -0.00799561f, -0.00393677f, 0.02865601f, -0.10794067f, 0.41287231f,
+ 0.78741455f, -0.15377808f, 0.04388428f, -0.00805664f, -0.00381470f, 0.02795410f,
+ -0.10559082f, 0.40219116f, 0.79598999f, -0.15338135f, 0.04382324f, -0.00808716f,
+ -0.00366211f, 0.02725220f, -0.10321045f, 0.39154053f, 0.80441284f, -0.15283203f,
+ 0.04373169f, -0.00811768f, -0.00354004f, 0.02655029f, -0.10079956f, 0.38092041f,
+ 0.81271362f, -0.15219116f, 0.04357910f, -0.00814819f, -0.00341797f, 0.02584839f,
+ -0.09838867f, 0.37033081f, 0.82086182f, -0.15142822f, 0.04342651f, -0.00814819f,
+ -0.00326538f, 0.02514648f, -0.09594727f, 0.35977173f, 0.82888794f, -0.15054321f,
+ 0.04321289f, -0.00817871f, -0.00314331f, 0.02441406f, -0.09347534f, 0.34924316f,
+ 0.83673096f, -0.14953613f, 0.04299927f, -0.00817871f, -0.00302124f, 0.02371216f,
+ -0.09097290f, 0.33874512f, 0.84445190f, -0.14840698f, 0.04272461f, -0.00817871f,
+ -0.00289917f, 0.02297974f, -0.08847046f, 0.32833862f, 0.85202026f, -0.14715576f,
+ 0.04238892f, -0.00814819f, -0.00277710f, 0.02227783f, -0.08596802f, 0.31793213f,
+ 0.85940552f, -0.14578247f, 0.04205322f, -0.00814819f, -0.00265503f, 0.02154541f,
+ -0.08343506f, 0.30761719f, 0.86666870f, -0.14428711f, 0.04165649f, -0.00811768f,
+ -0.00253296f, 0.02084351f, -0.08090210f, 0.29733276f, 0.87374878f, -0.14263916f,
+ 0.04122925f, -0.00808716f, -0.00244141f, 0.02011108f, -0.07833862f, 0.28710938f,
+ 0.88064575f, -0.14089966f, 0.04077148f, -0.00802612f, -0.00231934f, 0.01940918f,
+ -0.07577515f, 0.27694702f, 0.88739014f, -0.13900757f, 0.04025269f, -0.00796509f,
+ -0.00219727f, 0.01870728f, -0.07321167f, 0.26687622f, 0.89395142f, -0.13696289f,
+ 0.03970337f, -0.00790405f, -0.00210571f, 0.01797485f, -0.07064819f, 0.25683594f,
+ 0.90036011f, -0.13479614f, 0.03909302f, -0.00784302f, -0.00198364f, 0.01727295f,
+ -0.06808472f, 0.24691772f, 0.90658569f, -0.13250732f, 0.03848267f, -0.00775146f,
+ -0.00189209f, 0.01657104f, -0.06555176f, 0.23703003f, 0.91262817f, -0.13006592f,
+ 0.03781128f, -0.00765991f, -0.00177002f, 0.01586914f, -0.06298828f, 0.22723389f,
+ 0.91848755f, -0.12750244f, 0.03707886f, -0.00753784f, -0.00167847f, 0.01519775f,
+ -0.06042480f, 0.21752930f, 0.92413330f, -0.12478638f, 0.03631592f, -0.00744629f,
+ -0.00158691f, 0.01449585f, -0.05786133f, 0.20791626f, 0.92962646f, -0.12194824f,
+ 0.03552246f, -0.00732422f, -0.00149536f, 0.01382446f, -0.05532837f, 0.19839478f,
+ 0.93490601f, -0.11895752f, 0.03466797f, -0.00717163f, -0.00140381f, 0.01315308f,
+ -0.05279541f, 0.18893433f, 0.94000244f, -0.11584473f, 0.03378296f, -0.00701904f,
+ -0.00131226f, 0.01248169f, -0.05029297f, 0.17959595f, 0.94491577f, -0.11254883f,
+ 0.03286743f, -0.00686646f, -0.00122070f, 0.01181030f, -0.04779053f, 0.17034912f,
+ 0.94961548f, -0.10916138f, 0.03189087f, -0.00671387f, -0.00112915f, 0.01113892f,
+ -0.04528809f, 0.16119385f, 0.95413208f, -0.10559082f, 0.03085327f, -0.00653076f,
+ -0.00106812f, 0.01049805f, -0.04281616f, 0.15213013f, 0.95843506f, -0.10189819f,
+ 0.02981567f, -0.00631714f, -0.00097656f, 0.00985718f, -0.04034424f, 0.14318848f,
+ 0.96252441f, -0.09805298f, 0.02868652f, -0.00613403f, -0.00091553f, 0.00924683f,
+ -0.03790283f, 0.13436890f, 0.96643066f, -0.09405518f, 0.02755737f, -0.00592041f,
+ -0.00082397f, 0.00860596f, -0.03549194f, 0.12561035f, 0.97012329f, -0.08993530f,
+ 0.02636719f, -0.00567627f, -0.00076294f, 0.00799561f, -0.03308105f, 0.11700439f,
+ 0.97360229f, -0.08566284f, 0.02511597f, -0.00543213f, -0.00070190f, 0.00738525f,
+ -0.03073120f, 0.10848999f, 0.97686768f, -0.08123779f, 0.02383423f, -0.00518799f,
+ -0.00061035f, 0.00680542f, -0.02835083f, 0.10012817f, 0.97991943f, -0.07666016f,
+ 0.02252197f, -0.00494385f, -0.00054932f, 0.00622559f, -0.02603149f, 0.09185791f,
+ 0.98278809f, -0.07192993f, 0.02114868f, -0.00463867f, -0.00048828f, 0.00564575f,
+ -0.02374268f, 0.08370972f, 0.98541260f, -0.06707764f, 0.01971436f, -0.00436401f,
+ -0.00042725f, 0.00506592f, -0.02145386f, 0.07568359f, 0.98782349f, -0.06207275f,
+ 0.01828003f, -0.00405884f, -0.00039673f, 0.00451660f, -0.01922607f, 0.06777954f,
+ 0.99002075f, -0.05691528f, 0.01678467f, -0.00375366f, -0.00033569f, 0.00396729f,
+ -0.01699829f, 0.05999756f, 0.99200439f, -0.05163574f, 0.01522827f, -0.00341797f,
+ -0.00027466f, 0.00344849f, -0.01483154f, 0.05233765f, 0.99377441f, -0.04620361f,
+ 0.01364136f, -0.00308228f, -0.00024414f, 0.00292969f, -0.01266479f, 0.04483032f,
+ 0.99533081f, -0.04061890f, 0.01202393f, -0.00274658f, -0.00018311f, 0.00241089f,
+ -0.01055908f, 0.03741455f, 0.99664307f, -0.03488159f, 0.01037598f, -0.00238037f,
+ -0.00015259f, 0.00192261f, -0.00845337f, 0.03018188f, 0.99774170f, -0.02899170f,
+ 0.00866699f, -0.00201416f, -0.00009155f, 0.00143433f, -0.00640869f, 0.02304077f,
+ 0.99862671f, -0.02297974f, 0.00689697f, -0.00161743f, -0.00006104f, 0.00097656f,
+ -0.00439453f, 0.01605225f, 0.99929810f, -0.01684570f, 0.00512695f, -0.00122070f,
+ -0.00003052f, 0.00051880f, -0.00241089f, 0.00918579f, 0.99975586f, -0.01052856f,
+ 0.00329590f, -0.00079346f, 0.00000000f, 0.00006104f, -0.00048828f, 0.00247192f,
+ 0.99996948f, -0.00408936f, 0.00143433f, -0.00036621f,
+ };
+
+ const auto get_lut = [&]() -> std::span<const f32> {
+ if (sample_rate_ratio <= 1.0f) {
+ return std::span<const f32>(lut2.data(), lut2.size());
+ } else if (sample_rate_ratio < 1.3f) {
+ return std::span<const f32>(lut1.data(), lut1.size());
+ } else {
+ return std::span<const f32>(lut0.data(), lut0.size());
+ }
+ };
+
+ auto lut{get_lut()};
+ u32 read_index{0};
+ for (u32 i = 0; i < samples_to_write; i++) {
+ const auto lut_index{(fraction.get_frac() >> 8) * 8};
+ const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
+ const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
+ const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
+ const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]};
+ const Common::FixedPoint<56, 8> sample4{input[read_index + 4] * lut[lut_index + 4]};
+ const Common::FixedPoint<56, 8> sample5{input[read_index + 5] * lut[lut_index + 5]};
+ const Common::FixedPoint<56, 8> sample6{input[read_index + 6] * lut[lut_index + 6]};
+ const Common::FixedPoint<56, 8> sample7{input[read_index + 7] * lut[lut_index + 7]};
+ output[i] = (sample0 + sample1 + sample2 + sample3 + sample4 + sample5 + sample6 + sample7)
+ .to_int_floor();
+ fraction += sample_rate_ratio;
+ read_index += static_cast<u32>(fraction.to_int_floor());
+ fraction.clear_int();
+ }
+}
+
+void Resample(std::span<s32> output, std::span<const s16> input,
+ const Common::FixedPoint<49, 15>& sample_rate_ratio,
+ Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write,
+ const SrcQuality src_quality) {
+
+ switch (src_quality) {
+ case SrcQuality::Low:
+ ResampleLowQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
+ break;
+ case SrcQuality::Medium:
+ ResampleNormalQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
+ break;
+ case SrcQuality::High:
+ ResampleHighQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
+ break;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/resample.h b/src/audio_core/renderer/command/resample/resample.h
new file mode 100644
index 000000000..ba9209b82
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/resample.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Resample an input buffer into an output buffer, according to the sample_rate_ratio.
+ *
+ * @param output - Output buffer.
+ * @param input - Input buffer.
+ * @param sample_rate_ratio - Ratio for resampling.
+ e.g 32000/48000 = 0.666 input samples read per output.
+ * @param fraction - Current read fraction, written to and should be passed back in for
+ * multiple calls.
+ * @param samples_to_write - Number of samples to write.
+ * @param src_quality - Resampling quality.
+ */
+void Resample(std::span<s32> output, std::span<const s16> input,
+ const Common::FixedPoint<49, 15>& sample_rate_ratio,
+ Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality);
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/upsample.cpp b/src/audio_core/renderer/command/resample/upsample.cpp
new file mode 100644
index 000000000..6c3ff31f7
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/upsample.cpp
@@ -0,0 +1,262 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/resample/upsample.h"
+#include "audio_core/renderer/upsampler/upsampler_info.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Upsampling impl. Input must be 8K, 16K or 32K, output is 48K.
+ *
+ * @param output - Output buffer.
+ * @param input - Input buffer.
+ * @param target_sample_count - Number of samples for output.
+ * @param state - Upsampler state, updated each call.
+ */
+static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
+ const u32 target_sample_count, const u32 source_sample_count,
+ UpsamplerState* state) {
+ constexpr u32 WindowSize = 10;
+ constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow1{
+ 51.93359375f, -18.80078125f, 9.73046875f, -5.33203125f, 2.84375f,
+ -1.41015625f, 0.62109375f, -0.2265625f, 0.0625f, -0.00390625f,
+ };
+ constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow2{
+ 105.35546875f, -24.52734375f, 11.9609375f, -6.515625f, 3.52734375f,
+ -1.796875f, 0.828125f, -0.32421875f, 0.1015625f, -0.015625f,
+ };
+ constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow3{
+ 122.08203125f, -16.47656250f, 7.68359375f, -4.15625000f, 2.26171875f,
+ -1.16796875f, 0.54687500f, -0.22265625f, 0.07421875f, -0.01171875f,
+ };
+ constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow4{
+ 23.73437500f, -9.62109375f, 5.07812500f, -2.78125000f, 1.46875000f,
+ -0.71484375f, 0.30859375f, -0.10546875f, 0.02734375f, 0.00000000f,
+ };
+ constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow5{
+ 80.62500000f, -24.67187500f, 12.44921875f, -6.80859375f, 3.66406250f,
+ -1.83984375f, 0.83203125f, -0.31640625f, 0.09375000f, -0.01171875f,
+ };
+
+ if (!state->initialized) {
+ switch (source_sample_count) {
+ case 40:
+ state->window_size = WindowSize;
+ state->ratio = 6.0f;
+ state->history.fill(0);
+ break;
+
+ case 80:
+ state->window_size = WindowSize;
+ state->ratio = 3.0f;
+ state->history.fill(0);
+ break;
+
+ case 160:
+ state->window_size = WindowSize;
+ state->ratio = 1.5f;
+ state->history.fill(0);
+ break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid upsampling source count {}!", source_sample_count);
+ // This continues anyway, but let's assume 160 for sanity
+ state->window_size = WindowSize;
+ state->ratio = 1.5f;
+ state->history.fill(0);
+ break;
+ }
+
+ state->history_input_index = 0;
+ state->history_output_index = 9;
+ state->history_start_index = 0;
+ state->history_end_index = UpsamplerState::HistorySize - 1;
+ state->initialized = true;
+ }
+
+ if (target_sample_count == 0) {
+ return;
+ }
+
+ u32 read_index{0};
+
+ auto increment = [&]() -> void {
+ state->history[state->history_input_index] = input[read_index++];
+ state->history_input_index =
+ static_cast<u16>((state->history_input_index + 1) % UpsamplerState::HistorySize);
+ state->history_output_index =
+ static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
+ };
+
+ auto calculate_sample = [&state](std::span<const Common::FixedPoint<24, 8>> coeffs1,
+ std::span<const Common::FixedPoint<24, 8>> coeffs2) -> s32 {
+ auto output_index{state->history_output_index};
+ auto start_pos{output_index - state->history_start_index + 1U};
+ auto end_pos{10U};
+
+ if (start_pos < 10) {
+ end_pos = start_pos;
+ }
+
+ u64 prev_contrib{0};
+ u32 coeff_index{0};
+ for (; coeff_index < end_pos; coeff_index++, output_index--) {
+ prev_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
+ coeffs1[coeff_index].to_raw();
+ }
+
+ auto end_index{state->history_end_index};
+ for (; start_pos < 9; start_pos++, coeff_index++, end_index--) {
+ prev_contrib += static_cast<u64>(state->history[end_index].to_raw()) *
+ coeffs1[coeff_index].to_raw();
+ }
+
+ output_index =
+ static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
+ start_pos = state->history_end_index - output_index + 1U;
+ end_pos = 10U;
+
+ if (start_pos < 10) {
+ end_pos = start_pos;
+ }
+
+ u64 next_contrib{0};
+ coeff_index = 0;
+ for (; coeff_index < end_pos; coeff_index++, output_index++) {
+ next_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
+ coeffs2[coeff_index].to_raw();
+ }
+
+ auto start_index{state->history_start_index};
+ for (; start_pos < 9; start_pos++, start_index++, coeff_index++) {
+ next_contrib += static_cast<u64>(state->history[start_index].to_raw()) *
+ coeffs2[coeff_index].to_raw();
+ }
+
+ return static_cast<s32>(((prev_contrib >> 15) + (next_contrib >> 15)) >> 8);
+ };
+
+ switch (state->ratio.to_int_floor()) {
+ // 40 -> 240
+ case 6:
+ for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
+ switch (state->sample_index) {
+ case 0:
+ increment();
+ output[write_index] = state->history[state->history_output_index].to_int_floor();
+ break;
+
+ case 1:
+ output[write_index] = calculate_sample(SincWindow3, SincWindow4);
+ break;
+
+ case 2:
+ output[write_index] = calculate_sample(SincWindow2, SincWindow1);
+ break;
+
+ case 3:
+ output[write_index] = calculate_sample(SincWindow5, SincWindow5);
+ break;
+
+ case 4:
+ output[write_index] = calculate_sample(SincWindow1, SincWindow2);
+ break;
+
+ case 5:
+ output[write_index] = calculate_sample(SincWindow4, SincWindow3);
+ break;
+ }
+ state->sample_index = static_cast<u8>((state->sample_index + 1) % 6);
+ }
+ break;
+
+ // 80 -> 240
+ case 3:
+ for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
+ switch (state->sample_index) {
+ case 0:
+ increment();
+ output[write_index] = state->history[state->history_output_index].to_int_floor();
+ break;
+
+ case 1:
+ output[write_index] = calculate_sample(SincWindow2, SincWindow1);
+ break;
+
+ case 2:
+ output[write_index] = calculate_sample(SincWindow1, SincWindow2);
+ break;
+ }
+ state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
+ }
+ break;
+
+ // 160 -> 240
+ default:
+ for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
+ switch (state->sample_index) {
+ case 0:
+ increment();
+ output[write_index] = state->history[state->history_output_index].to_int_floor();
+ break;
+
+ case 1:
+ output[write_index] = calculate_sample(SincWindow1, SincWindow2);
+ break;
+
+ case 2:
+ increment();
+ output[write_index] = calculate_sample(SincWindow2, SincWindow1);
+ break;
+ }
+ state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
+ }
+
+ break;
+ }
+}
+
+auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) -> void {
+ string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}",
+ source_sample_count, source_sample_rate);
+ const auto upsampler{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
+ if (upsampler != nullptr) {
+ string += fmt::format("\n\tUpsampler\n\t\tenabled {} sample count {}\n\tinputs: ",
+ upsampler->enabled, upsampler->sample_count);
+ for (u32 i = 0; i < upsampler->input_count; i++) {
+ string += fmt::format("{:02X}, ", upsampler->inputs[i]);
+ }
+ }
+ string += "\n";
+}
+
+void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) {
+ const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
+ const auto input_count{std::min(info->input_count, buffer_count)};
+ const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count};
+
+ for (u32 i = 0; i < input_count; i++) {
+ const auto channel{inputs_[i]};
+
+ if (channel >= 0 && channel < static_cast<s16>(processor.buffer_count)) {
+ auto state{&info->states[i]};
+ std::span<s32> output{
+ reinterpret_cast<s32*>(samples_buffer + info->sample_count * channel * sizeof(s32)),
+ info->sample_count};
+ auto input{processor.mix_buffers.subspan(channel * processor.sample_count,
+ processor.sample_count)};
+
+ SrcProcessFrame(output, input, info->sample_count, source_sample_count, state);
+ }
+ }
+}
+
+bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/upsample.h b/src/audio_core/renderer/command/resample/upsample.h
new file mode 100644
index 000000000..bfc94e8af
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/upsample.h
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for upsampling a mix buffer to 48Khz.
+ * Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz.
+ */
+struct UpsampleCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Pointer to the output samples buffer.
+ CpuAddr samples_buffer;
+ /// Pointer to input mix buffer indexes.
+ CpuAddr inputs;
+ /// Number of input mix buffers.
+ u32 buffer_count;
+ /// Unknown, unused.
+ u32 unk_20;
+ /// Source data sample count.
+ u32 source_sample_count;
+ /// Source data sample rate.
+ u32 source_sample_rate;
+ /// Pointer to the upsampler info for this command.
+ CpuAddr upsampler_info;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/sink/circular_buffer.cpp b/src/audio_core/renderer/command/sink/circular_buffer.cpp
new file mode 100644
index 000000000..ded5afc94
--- /dev/null
+++ b/src/audio_core/renderer/command/sink/circular_buffer.cpp
@@ -0,0 +1,48 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <vector>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/sink/circular_buffer.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer {
+
+void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format(
+ "CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ",
+ input_count, size, pos);
+ for (u32 i = 0; i < input_count; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n";
+}
+
+void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
+ constexpr s32 min{std::numeric_limits<s16>::min()};
+ constexpr s32 max{std::numeric_limits<s16>::max()};
+
+ std::vector<s16> output(processor.sample_count);
+ for (u32 channel = 0; channel < input_count; channel++) {
+ auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count,
+ processor.sample_count)};
+ for (u32 sample_index = 0; sample_index < processor.sample_count; sample_index++) {
+ output[sample_index] = static_cast<s16>(std::clamp(input[sample_index], min, max));
+ }
+
+ processor.memory->WriteBlockUnsafe(address + pos, output.data(),
+ output.size() * sizeof(s16));
+ pos += static_cast<u32>(processor.sample_count * sizeof(s16));
+ if (pos >= size) {
+ pos = 0;
+ }
+ }
+}
+
+bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/sink/circular_buffer.h b/src/audio_core/renderer/command/sink/circular_buffer.h
new file mode 100644
index 000000000..e7d5be26e
--- /dev/null
+++ b/src/audio_core/renderer/command/sink/circular_buffer.h
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for sinking samples to a circular buffer.
+ */
+struct CircularBufferSinkCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Number of input mix buffers
+ u32 input_count;
+ /// Input mix buffer indexes
+ std::array<s16, MaxChannels> inputs;
+ /// Circular buffer address
+ CpuAddr address;
+ /// Circular buffer size
+ u32 size;
+ /// Current buffer offset
+ u32 pos;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp
new file mode 100644
index 000000000..e88372a75
--- /dev/null
+++ b/src/audio_core/renderer/command/sink/device.cpp
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/sink/device.h"
+#include "audio_core/sink/sink.h"
+
+namespace AudioCore::AudioRenderer {
+
+void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ",
+ std::string_view(name), session_id, input_count);
+ for (u32 i = 0; i < input_count; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n";
+}
+
+void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
+ constexpr s32 min = std::numeric_limits<s16>::min();
+ constexpr s32 max = std::numeric_limits<s16>::max();
+
+ auto stream{processor.GetOutputSinkStream()};
+ stream->SetSystemChannels(input_count);
+
+ Sink::SinkBuffer out_buffer{
+ .frames{TargetSampleCount},
+ .frames_played{0},
+ .tag{0},
+ .consumed{false},
+ };
+
+ std::vector<s16> samples(out_buffer.frames * input_count);
+
+ for (u32 channel = 0; channel < input_count; channel++) {
+ const auto offset{inputs[channel] * out_buffer.frames};
+
+ for (u32 index = 0; index < out_buffer.frames; index++) {
+ samples[index * input_count + channel] =
+ static_cast<s16>(std::clamp(sample_buffer[offset + index], min, max));
+ }
+ }
+
+ out_buffer.tag = reinterpret_cast<u64>(samples.data());
+ stream->AppendBuffer(out_buffer, samples);
+
+ if (stream->IsPaused()) {
+ stream->Start();
+ }
+}
+
+bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/sink/device.h b/src/audio_core/renderer/command/sink/device.h
new file mode 100644
index 000000000..1099bcf8c
--- /dev/null
+++ b/src/audio_core/renderer/command/sink/device.h
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <span>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for sinking samples to an output device.
+ */
+struct DeviceSinkCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Device name
+ char name[0x100];
+ /// System session id (unused)
+ s32 session_id;
+ /// Sample buffer to sink
+ std::span<s32> sample_buffer;
+ /// Number of input channels
+ u32 input_count;
+ /// Mix buffer indexes for each channel
+ std::array<s16, MaxChannels> inputs;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/aux_.cpp b/src/audio_core/renderer/effect/aux_.cpp
new file mode 100644
index 000000000..51e780ef1
--- /dev/null
+++ b/src/audio_core/renderer/effect/aux_.cpp
@@ -0,0 +1,93 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/aux_.h"
+
+namespace AudioCore::AudioRenderer {
+
+void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+ if (buffer_unmapped || in_params.is_new) {
+ const bool send_unmapped{!pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_specific->send_buffer_info_address,
+ sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))};
+ const bool return_unmapped{!pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[1], in_specific->return_buffer_info_address,
+ sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))};
+
+ buffer_unmapped = send_unmapped || return_unmapped;
+
+ if (!buffer_unmapped) {
+ auto send{workbuffers[0].GetReference(false)};
+ send_buffer_info = send + sizeof(AuxInfoDsp);
+ send_buffer = send + sizeof(AuxBufferInfo);
+
+ auto ret{workbuffers[1].GetReference(false)};
+ return_buffer_info = ret + sizeof(AuxInfoDsp);
+ return_buffer = ret + sizeof(AuxBufferInfo);
+ }
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion2));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (buffer_unmapped || in_params.is_new) {
+ const bool send_unmapped{!pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], params->send_buffer_info_address,
+ sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))};
+ const bool return_unmapped{!pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[1], params->return_buffer_info_address,
+ sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))};
+
+ buffer_unmapped = send_unmapped || return_unmapped;
+
+ if (!buffer_unmapped) {
+ auto send{workbuffers[0].GetReference(false)};
+ send_buffer_info = send + sizeof(AuxInfoDsp);
+ send_buffer = send + sizeof(AuxBufferInfo);
+
+ auto ret{workbuffers[1].GetReference(false)};
+ return_buffer_info = ret + sizeof(AuxInfoDsp);
+ return_buffer = ret + sizeof(AuxBufferInfo);
+ }
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void AuxInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+}
+
+void AuxInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void AuxInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
+
+CpuAddr AuxInfo::GetWorkbuffer(s32 index) {
+ return workbuffers[index].GetReference(true);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/aux_.h b/src/audio_core/renderer/effect/aux_.h
new file mode 100644
index 000000000..4d3d9e3d9
--- /dev/null
+++ b/src/audio_core/renderer/effect/aux_.h
@@ -0,0 +1,123 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Auxiliary Buffer used for Aux commands.
+ * Send and return buffers are available (names from the game's perspective).
+ * Send is read by the host, containing a buffer of samples to be used for whatever purpose.
+ * Return is written by the host, writing a mix buffer back to the game.
+ * This allows the game to use pre-processed samples skipping the other render processing,
+ * and to examine or modify what the audio renderer has generated.
+ */
+class AuxInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
+ /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
+ /* 0x30 */ u32 mix_buffer_count;
+ /* 0x34 */ u32 sample_rate;
+ /* 0x38 */ u32 count_max;
+ /* 0x3C */ u32 mix_buffer_count_max;
+ /* 0x40 */ CpuAddr send_buffer_info_address;
+ /* 0x48 */ CpuAddr send_buffer_address;
+ /* 0x50 */ CpuAddr return_buffer_info_address;
+ /* 0x58 */ CpuAddr return_buffer_address;
+ /* 0x60 */ u32 mix_buffer_sample_size;
+ /* 0x64 */ u32 sample_count;
+ /* 0x68 */ u32 mix_buffer_sample_count;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "AuxInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
+ /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
+ /* 0x30 */ u32 mix_buffer_count;
+ /* 0x34 */ u32 sample_rate;
+ /* 0x38 */ u32 count_max;
+ /* 0x3C */ u32 mix_buffer_count_max;
+ /* 0x40 */ CpuAddr send_buffer_info_address;
+ /* 0x48 */ CpuAddr send_buffer_address;
+ /* 0x50 */ CpuAddr return_buffer_info_address;
+ /* 0x58 */ CpuAddr return_buffer_address;
+ /* 0x60 */ u32 mix_buffer_sample_size;
+ /* 0x64 */ u32 sample_count;
+ /* 0x68 */ u32 mix_buffer_sample_count;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "AuxInfo::ParameterVersion2 has the wrong size!");
+
+ struct AuxInfoDsp {
+ /* 0x00 */ u32 read_offset;
+ /* 0x04 */ u32 write_offset;
+ /* 0x08 */ u32 lost_sample_count;
+ /* 0x0C */ u32 total_sample_count;
+ /* 0x10 */ char unk10[0x30];
+ };
+ static_assert(sizeof(AuxInfoDsp) == 0x40, "AuxInfo::AuxInfoDsp has the wrong size!");
+
+ struct AuxBufferInfo {
+ /* 0x00 */ AuxInfoDsp cpu_info;
+ /* 0x40 */ AuxInfoDsp dsp_info;
+ };
+ static_assert(sizeof(AuxBufferInfo) == 0x80, "AuxInfo::AuxBufferInfo has the wrong size!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/biquad_filter.cpp b/src/audio_core/renderer/effect/biquad_filter.cpp
new file mode 100644
index 000000000..a1efb3231
--- /dev/null
+++ b/src/audio_core/renderer/effect/biquad_filter.cpp
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/biquad_filter.h"
+
+namespace AudioCore::AudioRenderer {
+
+void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion2));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void BiquadFilterInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+ params->state = ParameterState::Updated;
+}
+
+void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state,
+ EffectResultState& dsp_state) {}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/biquad_filter.h b/src/audio_core/renderer/effect/biquad_filter.h
new file mode 100644
index 000000000..f53fd5bab
--- /dev/null
+++ b/src/audio_core/renderer/effect/biquad_filter.h
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+class BiquadFilterInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ std::array<s16, 3> b;
+ /* 0x12 */ std::array<s16, 2> a;
+ /* 0x16 */ s8 channel_count;
+ /* 0x17 */ ParameterState state;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "BiquadFilterInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ std::array<s16, 3> b;
+ /* 0x12 */ std::array<s16, 2> a;
+ /* 0x16 */ s8 channel_count;
+ /* 0x17 */ ParameterState state;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "BiquadFilterInfo::ParameterVersion2 has the wrong size!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/buffer_mixer.cpp b/src/audio_core/renderer/effect/buffer_mixer.cpp
new file mode 100644
index 000000000..9c8877f01
--- /dev/null
+++ b/src/audio_core/renderer/effect/buffer_mixer.cpp
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/buffer_mixer.h"
+
+namespace AudioCore::AudioRenderer {
+
+void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion2));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void BufferMixerInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+}
+
+void BufferMixerInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state,
+ EffectResultState& dsp_state) {}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/buffer_mixer.h b/src/audio_core/renderer/effect/buffer_mixer.h
new file mode 100644
index 000000000..23eed4a8b
--- /dev/null
+++ b/src/audio_core/renderer/effect/buffer_mixer.h
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+class BufferMixerInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
+ /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
+ /* 0x30 */ std::array<f32, MaxMixBuffers> volumes;
+ /* 0x90 */ u32 mix_count;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "BufferMixerInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
+ /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
+ /* 0x30 */ std::array<f32, MaxMixBuffers> volumes;
+ /* 0x90 */ u32 mix_count;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "BufferMixerInfo::ParameterVersion2 has the wrong size!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/capture.cpp b/src/audio_core/renderer/effect/capture.cpp
new file mode 100644
index 000000000..3f038efdb
--- /dev/null
+++ b/src/audio_core/renderer/effect/capture.cpp
@@ -0,0 +1,82 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/aux_.h"
+#include "audio_core/renderer/effect/capture.h"
+
+namespace AudioCore::AudioRenderer {
+
+void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{
+ reinterpret_cast<const AuxInfo::ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<AuxInfo::ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+ if (buffer_unmapped || in_params.is_new) {
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_specific->send_buffer_info_address,
+ in_specific->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo));
+
+ if (!buffer_unmapped) {
+ const auto send_address{workbuffers[0].GetReference(false)};
+ send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp);
+ send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo);
+ return_buffer_info = 0;
+ return_buffer = 0;
+ }
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{
+ reinterpret_cast<const AuxInfo::ParameterVersion2*>(in_params.specific.data())};
+ auto params{reinterpret_cast<AuxInfo::ParameterVersion2*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion2));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (buffer_unmapped || in_params.is_new) {
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], params->send_buffer_info_address,
+ params->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo));
+
+ if (!buffer_unmapped) {
+ const auto send_address{workbuffers[0].GetReference(false)};
+ send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp);
+ send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo);
+ return_buffer_info = 0;
+ return_buffer = 0;
+ }
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void CaptureInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+}
+
+void CaptureInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void CaptureInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
+
+CpuAddr CaptureInfo::GetWorkbuffer(s32 index) {
+ return workbuffers[index].GetReference(true);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/capture.h b/src/audio_core/renderer/effect/capture.h
new file mode 100644
index 000000000..6fbed8e6b
--- /dev/null
+++ b/src/audio_core/renderer/effect/capture.h
@@ -0,0 +1,65 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+class CaptureInfo : public EffectInfoBase {
+public:
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/compressor.cpp b/src/audio_core/renderer/effect/compressor.cpp
new file mode 100644
index 000000000..220ae02f9
--- /dev/null
+++ b/src/audio_core/renderer/effect/compressor.cpp
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/compressor.h"
+
+namespace AudioCore::AudioRenderer {
+
+void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {}
+
+void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void CompressorInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+ params->state = ParameterState::Updated;
+}
+
+CpuAddr CompressorInfo::GetWorkbuffer(s32 index) {
+ return GetSingleBuffer(index);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/compressor.h b/src/audio_core/renderer/effect/compressor.h
new file mode 100644
index 000000000..019a5ae58
--- /dev/null
+++ b/src/audio_core/renderer/effect/compressor.h
@@ -0,0 +1,106 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+
+class CompressorInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ s16 channel_count_max;
+ /* 0x0E */ s16 channel_count;
+ /* 0x10 */ s32 sample_rate;
+ /* 0x14 */ f32 threshold;
+ /* 0x18 */ f32 compressor_ratio;
+ /* 0x1C */ s32 attack_time;
+ /* 0x20 */ s32 release_time;
+ /* 0x24 */ f32 unk_24;
+ /* 0x28 */ f32 unk_28;
+ /* 0x2C */ f32 unk_2C;
+ /* 0x30 */ f32 out_gain;
+ /* 0x34 */ ParameterState state;
+ /* 0x35 */ bool makeup_gain_enabled;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "CompressorInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ s16 channel_count_max;
+ /* 0x0E */ s16 channel_count;
+ /* 0x10 */ s32 sample_rate;
+ /* 0x14 */ f32 threshold;
+ /* 0x18 */ f32 compressor_ratio;
+ /* 0x1C */ s32 attack_time;
+ /* 0x20 */ s32 release_time;
+ /* 0x24 */ f32 unk_24;
+ /* 0x28 */ f32 unk_28;
+ /* 0x2C */ f32 unk_2C;
+ /* 0x30 */ f32 out_gain;
+ /* 0x34 */ ParameterState state;
+ /* 0x35 */ bool makeup_gain_enabled;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "CompressorInfo::ParameterVersion2 has the wrong size!");
+
+ struct State {
+ f32 unk_00;
+ f32 unk_04;
+ f32 unk_08;
+ f32 unk_0C;
+ f32 unk_10;
+ f32 unk_14;
+ f32 unk_18;
+ f32 makeup_gain;
+ f32 unk_20;
+ char unk_24[0x1C];
+ };
+ static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
+ "CompressorInfo::State has the wrong size!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/delay.cpp b/src/audio_core/renderer/effect/delay.cpp
new file mode 100644
index 000000000..d9853efd9
--- /dev/null
+++ b/src/audio_core/renderer/effect/delay.cpp
@@ -0,0 +1,93 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/delay.h"
+
+namespace AudioCore::AudioRenderer {
+
+void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ if (IsChannelCountValid(in_specific->channel_count_max)) {
+ const auto old_state{params->state};
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (!IsChannelCountValid(in_specific->channel_count)) {
+ params->channel_count = params->channel_count_max;
+ }
+
+ if (!IsChannelCountValid(in_specific->channel_count) ||
+ old_state != ParameterState::Updated) {
+ params->state = old_state;
+ }
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ return;
+ }
+ }
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ if (IsChannelCountValid(in_specific->channel_count_max)) {
+ const auto old_state{params->state};
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (!IsChannelCountValid(in_specific->channel_count)) {
+ params->channel_count = params->channel_count_max;
+ }
+
+ if (!IsChannelCountValid(in_specific->channel_count) ||
+ old_state != ParameterState::Updated) {
+ params->state = old_state;
+ }
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ return;
+ }
+ }
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void DelayInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+ params->state = ParameterState::Updated;
+}
+
+void DelayInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void DelayInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
+
+CpuAddr DelayInfo::GetWorkbuffer(s32 index) {
+ return GetSingleBuffer(index);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/delay.h b/src/audio_core/renderer/effect/delay.h
new file mode 100644
index 000000000..accc42a06
--- /dev/null
+++ b/src/audio_core/renderer/effect/delay.h
@@ -0,0 +1,135 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+
+class DelayInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x10 */ u32 delay_time_max;
+ /* 0x14 */ u32 delay_time;
+ /* 0x18 */ Common::FixedPoint<18, 14> sample_rate;
+ /* 0x1C */ Common::FixedPoint<18, 14> in_gain;
+ /* 0x20 */ Common::FixedPoint<18, 14> feedback_gain;
+ /* 0x24 */ Common::FixedPoint<18, 14> wet_gain;
+ /* 0x28 */ Common::FixedPoint<18, 14> dry_gain;
+ /* 0x2C */ Common::FixedPoint<18, 14> channel_spread;
+ /* 0x30 */ Common::FixedPoint<18, 14> lowpass_amount;
+ /* 0x34 */ ParameterState state;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "DelayInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ s16 channel_count_max;
+ /* 0x0E */ s16 channel_count;
+ /* 0x10 */ s32 delay_time_max;
+ /* 0x14 */ s32 delay_time;
+ /* 0x18 */ s32 sample_rate;
+ /* 0x1C */ s32 in_gain;
+ /* 0x20 */ s32 feedback_gain;
+ /* 0x24 */ s32 wet_gain;
+ /* 0x28 */ s32 dry_gain;
+ /* 0x2C */ s32 channel_spread;
+ /* 0x30 */ s32 lowpass_amount;
+ /* 0x34 */ ParameterState state;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "DelayInfo::ParameterVersion2 has the wrong size!");
+
+ struct DelayLine {
+ Common::FixedPoint<50, 14> Read() const {
+ return buffer[buffer_pos];
+ }
+
+ void Write(const Common::FixedPoint<50, 14> value) {
+ buffer[buffer_pos] = value;
+ buffer_pos = static_cast<u32>((buffer_pos + 1) % buffer.size());
+ }
+
+ s32 sample_count_max{};
+ s32 sample_count{};
+ std::vector<Common::FixedPoint<50, 14>> buffer{};
+ u32 buffer_pos{};
+ Common::FixedPoint<18, 14> decay_rate{};
+ };
+
+ struct State {
+ /* 0x000 */ std::array<s32, 8> unk_000;
+ /* 0x020 */ std::array<DelayLine, MaxChannels> delay_lines;
+ /* 0x0B0 */ Common::FixedPoint<18, 14> feedback_gain;
+ /* 0x0B4 */ Common::FixedPoint<18, 14> delay_feedback_gain;
+ /* 0x0B8 */ Common::FixedPoint<18, 14> delay_feedback_cross_gain;
+ /* 0x0BC */ Common::FixedPoint<18, 14> lowpass_gain;
+ /* 0x0C0 */ Common::FixedPoint<18, 14> lowpass_feedback_gain;
+ /* 0x0C4 */ std::array<Common::FixedPoint<50, 14>, MaxChannels> lowpass_z;
+ };
+ static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
+ "DelayInfo::State has the wrong size!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_context.cpp b/src/audio_core/renderer/effect/effect_context.cpp
new file mode 100644
index 000000000..74c7801c9
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_context.cpp
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/effect_context.h"
+
+namespace AudioCore::AudioRenderer {
+
+void EffectContext::Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_,
+ std::span<EffectResultState> result_states_cpu_,
+ std::span<EffectResultState> result_states_dsp_,
+ const size_t dsp_state_count_) {
+ effect_infos = effect_infos_;
+ effect_count = effect_count_;
+ result_states_cpu = result_states_cpu_;
+ result_states_dsp = result_states_dsp_;
+ dsp_state_count = dsp_state_count_;
+}
+
+EffectInfoBase& EffectContext::GetInfo(const u32 index) {
+ return effect_infos[index];
+}
+
+EffectResultState& EffectContext::GetResultState(const u32 index) {
+ return result_states_cpu[index];
+}
+
+EffectResultState& EffectContext::GetDspSharedResultState(const u32 index) {
+ return result_states_dsp[index];
+}
+
+u32 EffectContext::GetCount() const {
+ return effect_count;
+}
+
+void EffectContext::UpdateStateByDspShared() {
+ for (size_t i = 0; i < dsp_state_count; i++) {
+ effect_infos[i].UpdateResultState(result_states_cpu[i], result_states_dsp[i]);
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_context.h b/src/audio_core/renderer/effect/effect_context.h
new file mode 100644
index 000000000..8f6d6e7d8
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_context.h
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "audio_core/renderer/effect/effect_result_state.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+class EffectContext {
+public:
+ /**
+ * Initialize the effect context
+ * @param effect_infos_ - List of effect infos for this context
+ * @param effect_count_ - The number of effects in the list
+ * @param result_states_cpu_ - The workbuffer of result states for the CPU for this context
+ * @param result_states_dsp_ - The workbuffer of result states for the DSP for this context
+ * @param dsp_state_count - The number of result states
+ */
+ void Initialize(std::span<EffectInfoBase> effect_infos_, u32 effect_count_,
+ std::span<EffectResultState> result_states_cpu_,
+ std::span<EffectResultState> result_states_dsp_, size_t dsp_state_count);
+
+ /**
+ * Get the EffectInfo for a given index
+ * @param index Which effect to return
+ * @return Pointer to the effect
+ */
+ EffectInfoBase& GetInfo(const u32 index);
+
+ /**
+ * Get the CPU result state for a given index
+ * @param index Which result to return
+ * @return Pointer to the effect result state
+ */
+ EffectResultState& GetResultState(const u32 index);
+
+ /**
+ * Get the DSP result state for a given index
+ * @param index Which result to return
+ * @return Pointer to the effect result state
+ */
+ EffectResultState& GetDspSharedResultState(const u32 index);
+
+ /**
+ * Get the number of effects in this context
+ * @return The number of effects
+ */
+ u32 GetCount() const;
+
+ /**
+ * Update the CPU and DSP result states for all effects
+ */
+ void UpdateStateByDspShared();
+
+private:
+ /// Workbuffer for all of the effects
+ std::span<EffectInfoBase> effect_infos{};
+ /// Number of effects in the workbuffer
+ u32 effect_count{};
+ /// Workbuffer of states for all effects, kept host-side and not directly modified, dsp states
+ /// are copied here on the next render frame
+ std::span<EffectResultState> result_states_cpu{};
+ /// Workbuffer of states for all effects, used by the AudioRenderer to track effect state
+ /// between calls
+ std::span<EffectResultState> result_states_dsp{};
+ /// Number of result states in the workbuffers
+ size_t dsp_state_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_info_base.h b/src/audio_core/renderer/effect/effect_info_base.h
new file mode 100644
index 000000000..8525fde05
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_info_base.h
@@ -0,0 +1,435 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/effect/effect_result_state.h"
+#include "audio_core/renderer/memory/address_info.h"
+#include "audio_core/renderer/memory/pool_mapper.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Base of all effects. Holds various data and functions used for all derived effects.
+ * Should not be used directly.
+ */
+class EffectInfoBase {
+public:
+ enum class Type : u8 {
+ Invalid,
+ Mix,
+ Aux,
+ Delay,
+ Reverb,
+ I3dl2Reverb,
+ BiquadFilter,
+ LightLimiter,
+ Capture,
+ Compressor,
+ };
+
+ enum class UsageState {
+ Invalid,
+ New,
+ Enabled,
+ Disabled,
+ };
+
+ enum class OutStatus : u8 {
+ Invalid,
+ New,
+ Initialized,
+ Used,
+ Removed,
+ };
+
+ enum class ParameterState : u8 {
+ Initialized,
+ Updating,
+ Updated,
+ };
+
+ struct InParameterVersion1 {
+ /* 0x00 */ Type type;
+ /* 0x01 */ bool is_new;
+ /* 0x02 */ bool enabled;
+ /* 0x04 */ u32 mix_id;
+ /* 0x08 */ CpuAddr workbuffer;
+ /* 0x10 */ CpuAddr workbuffer_size;
+ /* 0x18 */ u32 process_order;
+ /* 0x1C */ char unk1C[0x4];
+ /* 0x20 */ std::array<u8, 0xA0> specific;
+ };
+ static_assert(sizeof(InParameterVersion1) == 0xC0,
+ "EffectInfoBase::InParameterVersion1 has the wrong size!");
+
+ struct InParameterVersion2 {
+ /* 0x00 */ Type type;
+ /* 0x01 */ bool is_new;
+ /* 0x02 */ bool enabled;
+ /* 0x04 */ u32 mix_id;
+ /* 0x08 */ CpuAddr workbuffer;
+ /* 0x10 */ CpuAddr workbuffer_size;
+ /* 0x18 */ u32 process_order;
+ /* 0x1C */ char unk1C[0x4];
+ /* 0x20 */ std::array<u8, 0xA0> specific;
+ };
+ static_assert(sizeof(InParameterVersion2) == 0xC0,
+ "EffectInfoBase::InParameterVersion2 has the wrong size!");
+
+ struct OutStatusVersion1 {
+ /* 0x00 */ OutStatus state;
+ /* 0x01 */ char unk01[0xF];
+ };
+ static_assert(sizeof(OutStatusVersion1) == 0x10,
+ "EffectInfoBase::OutStatusVersion1 has the wrong size!");
+
+ struct OutStatusVersion2 {
+ /* 0x00 */ OutStatus state;
+ /* 0x01 */ char unk01[0xF];
+ /* 0x10 */ EffectResultState result_state;
+ };
+ static_assert(sizeof(OutStatusVersion2) == 0x90,
+ "EffectInfoBase::OutStatusVersion2 has the wrong size!");
+
+ struct State {
+ std::array<u8, 0x500> buffer;
+ };
+ static_assert(sizeof(State) == 0x500, "EffectInfoBase::State has the wrong size!");
+
+ EffectInfoBase() {
+ Cleanup();
+ }
+
+ virtual ~EffectInfoBase() = default;
+
+ /**
+ * Cleanup this effect, resetting it to a starting state.
+ */
+ void Cleanup() {
+ type = Type::Invalid;
+ enabled = false;
+ mix_id = UnusedMixId;
+ process_order = InvalidProcessOrder;
+ buffer_unmapped = false;
+ parameter = {};
+ for (auto& workbuffer : workbuffers) {
+ workbuffer.Setup(CpuAddr(0), 0);
+ }
+ }
+
+ /**
+ * Forcibly unmap all assigned workbuffers from the AudioRenderer.
+ *
+ * @param pool_mapper - Mapper to unmap the buffers.
+ */
+ void ForceUnmapBuffers(const PoolMapper& pool_mapper) {
+ for (auto& workbuffer : workbuffers) {
+ if (workbuffer.GetReference(false) != 0) {
+ pool_mapper.ForceUnmapPointer(workbuffer);
+ }
+ }
+ }
+
+ /**
+ * Check if this effect is enabled.
+ *
+ * @return True if effect is enabled, otherwise false.
+ */
+ bool IsEnabled() const {
+ return enabled;
+ }
+
+ /**
+ * Check if this effect should not be generated.
+ *
+ * @return True if effect should be skipped, otherwise false.
+ */
+ bool ShouldSkip() const {
+ return buffer_unmapped;
+ }
+
+ /**
+ * Get the type of this effect.
+ *
+ * @return The type of this effect. See EffectInfoBase::Type
+ */
+ Type GetType() const {
+ return type;
+ }
+
+ /**
+ * Set the type of this effect.
+ *
+ * @param type_ - The new type of this effect.
+ */
+ void SetType(const Type type_) {
+ type = type_;
+ }
+
+ /**
+ * Get the mix id of this effect.
+ *
+ * @return Mix id of this effect.
+ */
+ s32 GetMixId() const {
+ return mix_id;
+ }
+
+ /**
+ * Get the processing order of this effect.
+ *
+ * @return Process order of this effect.
+ */
+ s32 GetProcessingOrder() const {
+ return process_order;
+ }
+
+ /**
+ * Get this effect's parameter data.
+ *
+ * @return Pointer to the parametter, must be cast to the correct type.
+ */
+ u8* GetParameter() {
+ return parameter.data();
+ }
+
+ /**
+ * Get this effect's parameter data.
+ *
+ * @return Pointer to the parametter, must be cast to the correct type.
+ */
+ u8* GetStateBuffer() {
+ return state.data();
+ }
+
+ /**
+ * Set this effect's usage state.
+ *
+ * @param usage - new usage state of this effect.
+ */
+ void SetUsage(const UsageState usage) {
+ usage_state = usage;
+ }
+
+ /**
+ * Check if this effects need to have its workbuffer information updated.
+ * Version 1.
+ *
+ * @param params - Input parameters.
+ * @return True if workbuffers need updating, otherwise false.
+ */
+ bool ShouldUpdateWorkBufferInfo(const InParameterVersion1& params) const {
+ return buffer_unmapped || params.is_new;
+ }
+
+ /**
+ * Check if this effects need to have its workbuffer information updated.
+ * Version 2.
+ *
+ * @param params - Input parameters.
+ * @return True if workbuffers need updating, otherwise false.
+ */
+ bool ShouldUpdateWorkBufferInfo(const InParameterVersion2& params) const {
+ return buffer_unmapped || params.is_new;
+ }
+
+ /**
+ * Get the current usage state of this effect.
+ *
+ * @return The current usage state.
+ */
+ UsageState GetUsage() const {
+ return usage_state;
+ }
+
+ /**
+ * Write the current state. Version 1.
+ *
+ * @param out_status - Status to write.
+ * @param renderer_active - Is the AudioRenderer active?
+ */
+ void StoreStatus(OutStatusVersion1& out_status, const bool renderer_active) const {
+ if (renderer_active) {
+ if (usage_state != UsageState::Disabled) {
+ out_status.state = OutStatus::Used;
+ } else {
+ out_status.state = OutStatus::Removed;
+ }
+ } else if (usage_state == UsageState::New) {
+ out_status.state = OutStatus::Used;
+ } else {
+ out_status.state = OutStatus::Removed;
+ }
+ }
+
+ /**
+ * Write the current state. Version 2.
+ *
+ * @param out_status - Status to write.
+ * @param renderer_active - Is the AudioRenderer active?
+ */
+ void StoreStatus(OutStatusVersion2& out_status, const bool renderer_active) const {
+ if (renderer_active) {
+ if (usage_state != UsageState::Disabled) {
+ out_status.state = OutStatus::Used;
+ } else {
+ out_status.state = OutStatus::Removed;
+ }
+ } else if (usage_state == UsageState::New) {
+ out_status.state = OutStatus::Used;
+ } else {
+ out_status.state = OutStatus::Removed;
+ }
+ }
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ virtual void Update(BehaviorInfo::ErrorInfo& error_info,
+ [[maybe_unused]] const InParameterVersion1& params,
+ [[maybe_unused]] const PoolMapper& pool_mapper) {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ virtual void Update(BehaviorInfo::ErrorInfo& error_info,
+ [[maybe_unused]] const InParameterVersion2& params,
+ [[maybe_unused]] const PoolMapper& pool_mapper) {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ virtual void UpdateForCommandGeneration() {}
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ virtual void InitializeResultState([[maybe_unused]] EffectResultState& result_state) {}
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ virtual void UpdateResultState([[maybe_unused]] EffectResultState& cpu_state,
+ [[maybe_unused]] EffectResultState& dsp_state) {}
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ virtual CpuAddr GetWorkbuffer([[maybe_unused]] s32 index) {
+ return 0;
+ }
+
+ /**
+ * Get the first workbuffer assigned to this effect.
+ *
+ * @param index - Workbuffer index. Unused.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetSingleBuffer([[maybe_unused]] const s32 index) {
+ if (enabled) {
+ return workbuffers[0].GetReference(true);
+ }
+
+ if (usage_state != UsageState::Disabled) {
+ const auto ref{workbuffers[0].GetReference(false)};
+ const auto size{workbuffers[0].GetSize()};
+ if (ref != 0 && size > 0) {
+ // Invalidate DSP cache
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Get the send buffer info, used by Aux and Capture.
+ *
+ * @return Address of the buffer info.
+ */
+ CpuAddr GetSendBufferInfo() const {
+ return send_buffer_info;
+ }
+
+ /**
+ * Get the send buffer, used by Aux and Capture.
+ *
+ * @return Address of the buffer.
+ */
+ CpuAddr GetSendBuffer() const {
+ return send_buffer;
+ }
+
+ /**
+ * Get the return buffer info, used by Aux and Capture.
+ *
+ * @return Address of the buffer info.
+ */
+ CpuAddr GetReturnBufferInfo() const {
+ return return_buffer_info;
+ }
+
+ /**
+ * Get the return buffer, used by Aux and Capture.
+ *
+ * @return Address of the buffer.
+ */
+ CpuAddr GetReturnBuffer() const {
+ return return_buffer;
+ }
+
+protected:
+ /// Type of this effect. May be changed
+ Type type{Type::Invalid};
+ /// Is this effect enabled?
+ bool enabled{};
+ /// Are this effect's buffers unmapped?
+ bool buffer_unmapped{};
+ /// Current usage state
+ UsageState usage_state{UsageState::Invalid};
+ /// Mix id of this effect
+ s32 mix_id{UnusedMixId};
+ /// Process order of this effect
+ s32 process_order{InvalidProcessOrder};
+ /// Workbuffers assigned to this effect
+ std::array<AddressInfo, 2> workbuffers{AddressInfo(CpuAddr(0), 0), AddressInfo(CpuAddr(0), 0)};
+ /// Aux/Capture buffer info for reading
+ CpuAddr send_buffer_info{};
+ /// Aux/Capture buffer for reading
+ CpuAddr send_buffer{};
+ /// Aux/Capture buffer info for writing
+ CpuAddr return_buffer_info{};
+ /// Aux/Capture buffer for writing
+ CpuAddr return_buffer{};
+ /// Parameters of this effect
+ std::array<u8, sizeof(InParameterVersion2)> parameter{};
+ /// State of this effect used by the AudioRenderer across calls
+ std::array<u8, sizeof(State)> state{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_reset.h b/src/audio_core/renderer/effect/effect_reset.h
new file mode 100644
index 000000000..1ea67e334
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_reset.h
@@ -0,0 +1,71 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/effect/aux_.h"
+#include "audio_core/renderer/effect/biquad_filter.h"
+#include "audio_core/renderer/effect/buffer_mixer.h"
+#include "audio_core/renderer/effect/capture.h"
+#include "audio_core/renderer/effect/compressor.h"
+#include "audio_core/renderer/effect/delay.h"
+#include "audio_core/renderer/effect/i3dl2.h"
+#include "audio_core/renderer/effect/light_limiter.h"
+#include "audio_core/renderer/effect/reverb.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Reset an effect, and create a new one of the given type.
+ *
+ * @param effect - Effect to reset and re-construct.
+ * @param type - Type of the new effect to create.
+ */
+static void ResetEffect(EffectInfoBase* effect, const EffectInfoBase::Type type) {
+ *effect = {};
+
+ switch (type) {
+ case EffectInfoBase::Type::Invalid:
+ std::construct_at<EffectInfoBase>(effect);
+ effect->SetType(EffectInfoBase::Type::Invalid);
+ break;
+ case EffectInfoBase::Type::Mix:
+ std::construct_at<BufferMixerInfo>(reinterpret_cast<BufferMixerInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::Mix);
+ break;
+ case EffectInfoBase::Type::Aux:
+ std::construct_at<AuxInfo>(reinterpret_cast<AuxInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::Aux);
+ break;
+ case EffectInfoBase::Type::Delay:
+ std::construct_at<DelayInfo>(reinterpret_cast<DelayInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::Delay);
+ break;
+ case EffectInfoBase::Type::Reverb:
+ std::construct_at<ReverbInfo>(reinterpret_cast<ReverbInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::Reverb);
+ break;
+ case EffectInfoBase::Type::I3dl2Reverb:
+ std::construct_at<I3dl2ReverbInfo>(reinterpret_cast<I3dl2ReverbInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::I3dl2Reverb);
+ break;
+ case EffectInfoBase::Type::BiquadFilter:
+ std::construct_at<BiquadFilterInfo>(reinterpret_cast<BiquadFilterInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::BiquadFilter);
+ break;
+ case EffectInfoBase::Type::LightLimiter:
+ std::construct_at<LightLimiterInfo>(reinterpret_cast<LightLimiterInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::LightLimiter);
+ break;
+ case EffectInfoBase::Type::Capture:
+ std::construct_at<CaptureInfo>(reinterpret_cast<CaptureInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::Capture);
+ break;
+ case EffectInfoBase::Type::Compressor:
+ std::construct_at<CompressorInfo>(reinterpret_cast<CompressorInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::Compressor);
+ break;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_result_state.h b/src/audio_core/renderer/effect/effect_result_state.h
new file mode 100644
index 000000000..ae096ad69
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_result_state.h
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+struct EffectResultState {
+ std::array<u8, 0x80> state;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/i3dl2.cpp b/src/audio_core/renderer/effect/i3dl2.cpp
new file mode 100644
index 000000000..960b29cfc
--- /dev/null
+++ b/src/audio_core/renderer/effect/i3dl2.cpp
@@ -0,0 +1,94 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/i3dl2.h"
+
+namespace AudioCore::AudioRenderer {
+
+void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ if (IsChannelCountValid(in_specific->channel_count_max)) {
+ const auto old_state{params->state};
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (!IsChannelCountValid(in_specific->channel_count)) {
+ params->channel_count = params->channel_count_max;
+ }
+
+ if (!IsChannelCountValid(in_specific->channel_count) ||
+ old_state != ParameterState::Updated) {
+ params->state = old_state;
+ }
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ return;
+ }
+ }
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ if (IsChannelCountValid(in_specific->channel_count_max)) {
+ const auto old_state{params->state};
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (!IsChannelCountValid(in_specific->channel_count)) {
+ params->channel_count = params->channel_count_max;
+ }
+
+ if (!IsChannelCountValid(in_specific->channel_count) ||
+ old_state != ParameterState::Updated) {
+ params->state = old_state;
+ }
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ return;
+ }
+ }
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void I3dl2ReverbInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+ params->state = ParameterState::Updated;
+}
+
+void I3dl2ReverbInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void I3dl2ReverbInfo::UpdateResultState(EffectResultState& cpu_state,
+ EffectResultState& dsp_state) {}
+
+CpuAddr I3dl2ReverbInfo::GetWorkbuffer(s32 index) {
+ return GetSingleBuffer(index);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/i3dl2.h b/src/audio_core/renderer/effect/i3dl2.h
new file mode 100644
index 000000000..1ebbc5c4c
--- /dev/null
+++ b/src/audio_core/renderer/effect/i3dl2.h
@@ -0,0 +1,200 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+
+class I3dl2ReverbInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x10 */ char unk10[0x4];
+ /* 0x14 */ u32 sample_rate;
+ /* 0x18 */ f32 room_HF_gain;
+ /* 0x1C */ f32 reference_HF;
+ /* 0x20 */ f32 late_reverb_decay_time;
+ /* 0x24 */ f32 late_reverb_HF_decay_ratio;
+ /* 0x28 */ f32 room_gain;
+ /* 0x2C */ f32 reflection_gain;
+ /* 0x30 */ f32 reverb_gain;
+ /* 0x34 */ f32 late_reverb_diffusion;
+ /* 0x38 */ f32 reflection_delay;
+ /* 0x3C */ f32 late_reverb_delay_time;
+ /* 0x40 */ f32 late_reverb_density;
+ /* 0x44 */ f32 dry_gain;
+ /* 0x48 */ ParameterState state;
+ /* 0x49 */ char unk49[0x3];
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "I3dl2ReverbInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x10 */ char unk10[0x4];
+ /* 0x14 */ u32 sample_rate;
+ /* 0x18 */ f32 room_HF_gain;
+ /* 0x1C */ f32 reference_HF;
+ /* 0x20 */ f32 late_reverb_decay_time;
+ /* 0x24 */ f32 late_reverb_HF_decay_ratio;
+ /* 0x28 */ f32 room_gain;
+ /* 0x2C */ f32 reflection_gain;
+ /* 0x30 */ f32 reverb_gain;
+ /* 0x34 */ f32 late_reverb_diffusion;
+ /* 0x38 */ f32 reflection_delay;
+ /* 0x3C */ f32 late_reverb_delay_time;
+ /* 0x40 */ f32 late_reverb_density;
+ /* 0x44 */ f32 dry_gain;
+ /* 0x48 */ ParameterState state;
+ /* 0x49 */ char unk49[0x3];
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "I3dl2ReverbInfo::ParameterVersion2 has the wrong size!");
+
+ static constexpr u32 MaxDelayLines = 4;
+ static constexpr u32 MaxDelayTaps = 20;
+
+ struct I3dl2DelayLine {
+ void Initialize(const s32 delay_time) {
+ max_delay = delay_time;
+ buffer.resize(delay_time + 1, 0);
+ buffer_end = &buffer[delay_time];
+ output = &buffer[0];
+ SetDelay(delay_time);
+ wet_gain = 0.0f;
+ }
+
+ void SetDelay(const s32 delay_time) {
+ if (max_delay < delay_time) {
+ return;
+ }
+ delay = delay_time;
+ input = &buffer[(output - buffer.data() + delay) % (max_delay + 1)];
+ }
+
+ Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) {
+ Write(sample);
+
+ auto out_sample{Read()};
+
+ output++;
+ if (output >= buffer_end) {
+ output = buffer.data();
+ }
+
+ return out_sample;
+ }
+
+ Common::FixedPoint<50, 14> Read() const {
+ return *output;
+ }
+
+ void Write(const Common::FixedPoint<50, 14> sample) {
+ *(input++) = sample;
+ if (input >= buffer_end) {
+ input = buffer.data();
+ }
+ }
+
+ Common::FixedPoint<50, 14> TapOut(const s32 index) const {
+ auto out{input - (index + 1)};
+ if (out < buffer.data()) {
+ out += max_delay + 1;
+ }
+ return *out;
+ }
+
+ std::vector<Common::FixedPoint<50, 14>> buffer{};
+ Common::FixedPoint<50, 14>* buffer_end{};
+ s32 max_delay{};
+ Common::FixedPoint<50, 14>* input{};
+ Common::FixedPoint<50, 14>* output{};
+ s32 delay{};
+ f32 wet_gain{};
+ };
+
+ struct State {
+ f32 lowpass_0;
+ f32 lowpass_1;
+ f32 lowpass_2;
+ I3dl2DelayLine early_delay_line;
+ std::array<s32, MaxDelayTaps> early_tap_steps;
+ f32 early_gain;
+ f32 late_gain;
+ s32 early_to_late_taps;
+ std::array<I3dl2DelayLine, MaxDelayLines> fdn_delay_lines;
+ std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines0;
+ std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines1;
+ f32 last_reverb_echo;
+ I3dl2DelayLine center_delay_line;
+ std::array<std::array<f32, 3>, MaxDelayLines> lowpass_coeff;
+ std::array<f32, MaxDelayLines> shelf_filter;
+ f32 dry_gain;
+ };
+ static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
+ "I3dl2ReverbInfo::State is too large!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/light_limiter.cpp b/src/audio_core/renderer/effect/light_limiter.cpp
new file mode 100644
index 000000000..1635a952d
--- /dev/null
+++ b/src/audio_core/renderer/effect/light_limiter.cpp
@@ -0,0 +1,81 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/light_limiter.h"
+
+namespace AudioCore::AudioRenderer {
+
+void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void LightLimiterInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+ params->state = ParameterState::Updated;
+ params->statistics_reset_required = false;
+}
+
+void LightLimiterInfo::InitializeResultState(EffectResultState& result_state) {
+ auto result_state_{reinterpret_cast<StatisticsInternal*>(result_state.state.data())};
+
+ result_state_->channel_max_sample.fill(0);
+ result_state_->channel_compression_gain_min.fill(1.0f);
+}
+
+void LightLimiterInfo::UpdateResultState(EffectResultState& cpu_state,
+ EffectResultState& dsp_state) {
+ auto cpu_statistics{reinterpret_cast<StatisticsInternal*>(cpu_state.state.data())};
+ auto dsp_statistics{reinterpret_cast<StatisticsInternal*>(dsp_state.state.data())};
+
+ *cpu_statistics = *dsp_statistics;
+}
+
+CpuAddr LightLimiterInfo::GetWorkbuffer(s32 index) {
+ return GetSingleBuffer(index);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/light_limiter.h b/src/audio_core/renderer/effect/light_limiter.h
new file mode 100644
index 000000000..338d67bbc
--- /dev/null
+++ b/src/audio_core/renderer/effect/light_limiter.h
@@ -0,0 +1,138 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+
+class LightLimiterInfo : public EffectInfoBase {
+public:
+ enum class ProcessingMode {
+ Mode0,
+ Mode1,
+ };
+
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x0C */ u32 sample_rate;
+ /* 0x14 */ s32 look_ahead_time_max;
+ /* 0x18 */ s32 attack_time;
+ /* 0x1C */ s32 release_time;
+ /* 0x20 */ s32 look_ahead_time;
+ /* 0x24 */ f32 attack_coeff;
+ /* 0x28 */ f32 release_coeff;
+ /* 0x2C */ f32 threshold;
+ /* 0x30 */ f32 input_gain;
+ /* 0x34 */ f32 output_gain;
+ /* 0x38 */ s32 look_ahead_samples_min;
+ /* 0x3C */ s32 look_ahead_samples_max;
+ /* 0x40 */ ParameterState state;
+ /* 0x41 */ bool statistics_enabled;
+ /* 0x42 */ bool statistics_reset_required;
+ /* 0x43 */ ProcessingMode processing_mode;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "LightLimiterInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x0C */ u32 sample_rate;
+ /* 0x14 */ s32 look_ahead_time_max;
+ /* 0x18 */ s32 attack_time;
+ /* 0x1C */ s32 release_time;
+ /* 0x20 */ s32 look_ahead_time;
+ /* 0x24 */ f32 attack_coeff;
+ /* 0x28 */ f32 release_coeff;
+ /* 0x2C */ f32 threshold;
+ /* 0x30 */ f32 input_gain;
+ /* 0x34 */ f32 output_gain;
+ /* 0x38 */ s32 look_ahead_samples_min;
+ /* 0x3C */ s32 look_ahead_samples_max;
+ /* 0x40 */ ParameterState state;
+ /* 0x41 */ bool statistics_enabled;
+ /* 0x42 */ bool statistics_reset_required;
+ /* 0x43 */ ProcessingMode processing_mode;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "LightLimiterInfo::ParameterVersion2 has the wrong size!");
+
+ struct State {
+ std::array<Common::FixedPoint<49, 15>, MaxChannels> samples_average;
+ std::array<Common::FixedPoint<49, 15>, MaxChannels> compression_gain;
+ std::array<s32, MaxChannels> look_ahead_sample_offsets;
+ std::array<std::vector<Common::FixedPoint<49, 15>>, MaxChannels> look_ahead_sample_buffers;
+ };
+ static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
+ "LightLimiterInfo::State has the wrong size!");
+
+ struct StatisticsInternal {
+ /* 0x00 */ std::array<f32, MaxChannels> channel_max_sample;
+ /* 0x18 */ std::array<f32, MaxChannels> channel_compression_gain_min;
+ };
+ static_assert(sizeof(StatisticsInternal) == 0x30,
+ "LightLimiterInfo::StatisticsInternal has the wrong size!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new limiter statistics result state. Version 2 only.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side limiter statistics with the ADSP-side one. Version 2 only.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/reverb.cpp b/src/audio_core/renderer/effect/reverb.cpp
new file mode 100644
index 000000000..2d32383d0
--- /dev/null
+++ b/src/audio_core/renderer/effect/reverb.cpp
@@ -0,0 +1,93 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/reverb.h"
+
+namespace AudioCore::AudioRenderer {
+
+void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ if (IsChannelCountValid(in_specific->channel_count_max)) {
+ const auto old_state{params->state};
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (!IsChannelCountValid(in_specific->channel_count)) {
+ params->channel_count = params->channel_count_max;
+ }
+
+ if (!IsChannelCountValid(in_specific->channel_count) ||
+ old_state != ParameterState::Updated) {
+ params->state = old_state;
+ }
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ return;
+ }
+ }
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
+
+ if (IsChannelCountValid(in_specific->channel_count_max)) {
+ const auto old_state{params->state};
+ std::memcpy(params, in_specific, sizeof(ParameterVersion2));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (!IsChannelCountValid(in_specific->channel_count)) {
+ params->channel_count = params->channel_count_max;
+ }
+
+ if (!IsChannelCountValid(in_specific->channel_count) ||
+ old_state != ParameterState::Updated) {
+ params->state = old_state;
+ }
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ return;
+ }
+ }
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void ReverbInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+ params->state = ParameterState::Updated;
+}
+
+void ReverbInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void ReverbInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
+
+CpuAddr ReverbInfo::GetWorkbuffer(s32 index) {
+ return GetSingleBuffer(index);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/reverb.h b/src/audio_core/renderer/effect/reverb.h
new file mode 100644
index 000000000..a72475c3c
--- /dev/null
+++ b/src/audio_core/renderer/effect/reverb.h
@@ -0,0 +1,190 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+
+class ReverbInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x10 */ u32 sample_rate;
+ /* 0x14 */ u32 early_mode;
+ /* 0x18 */ s32 early_gain;
+ /* 0x1C */ s32 pre_delay;
+ /* 0x20 */ s32 late_mode;
+ /* 0x24 */ s32 late_gain;
+ /* 0x28 */ s32 decay_time;
+ /* 0x2C */ s32 high_freq_Decay_ratio;
+ /* 0x30 */ s32 colouration;
+ /* 0x34 */ s32 base_gain;
+ /* 0x38 */ s32 wet_gain;
+ /* 0x3C */ s32 dry_gain;
+ /* 0x40 */ ParameterState state;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "ReverbInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x10 */ u32 sample_rate;
+ /* 0x14 */ u32 early_mode;
+ /* 0x18 */ s32 early_gain;
+ /* 0x1C */ s32 pre_delay;
+ /* 0x20 */ s32 late_mode;
+ /* 0x24 */ s32 late_gain;
+ /* 0x28 */ s32 decay_time;
+ /* 0x2C */ s32 high_freq_decay_ratio;
+ /* 0x30 */ s32 colouration;
+ /* 0x34 */ s32 base_gain;
+ /* 0x38 */ s32 wet_gain;
+ /* 0x3C */ s32 dry_gain;
+ /* 0x40 */ ParameterState state;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "ReverbInfo::ParameterVersion2 has the wrong size!");
+
+ static constexpr u32 MaxDelayLines = 4;
+ static constexpr u32 MaxDelayTaps = 10;
+ static constexpr u32 NumEarlyModes = 5;
+ static constexpr u32 NumLateModes = 5;
+
+ struct ReverbDelayLine {
+ void Initialize(const s32 delay_time, const f32 decay_rate) {
+ buffer.resize(delay_time + 1, 0);
+ buffer_end = &buffer[delay_time];
+ output = &buffer[0];
+ decay = decay_rate;
+ sample_count_max = delay_time;
+ SetDelay(delay_time);
+ }
+
+ void SetDelay(const s32 delay_time) {
+ if (sample_count_max < delay_time) {
+ return;
+ }
+ sample_count = delay_time;
+ input = &buffer[(output - buffer.data() + sample_count) % (sample_count_max + 1)];
+ }
+
+ Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) {
+ Write(sample);
+
+ auto out_sample{Read()};
+
+ output++;
+ if (output >= buffer_end) {
+ output = buffer.data();
+ }
+
+ return out_sample;
+ }
+
+ Common::FixedPoint<50, 14> Read() const {
+ return *output;
+ }
+
+ void Write(const Common::FixedPoint<50, 14> sample) {
+ *(input++) = sample;
+ if (input >= buffer_end) {
+ input = buffer.data();
+ }
+ }
+
+ Common::FixedPoint<50, 14> TapOut(const s32 index) const {
+ auto out{input - (index + 1)};
+ if (out < buffer.data()) {
+ out += sample_count;
+ }
+ return *out;
+ }
+
+ s32 sample_count{};
+ s32 sample_count_max{};
+ std::vector<Common::FixedPoint<50, 14>> buffer{};
+ Common::FixedPoint<50, 14>* buffer_end;
+ Common::FixedPoint<50, 14>* input{};
+ Common::FixedPoint<50, 14>* output{};
+ Common::FixedPoint<50, 14> decay{};
+ };
+
+ struct State {
+ ReverbDelayLine pre_delay_line;
+ ReverbDelayLine center_delay_line;
+ std::array<s32, MaxDelayTaps> early_delay_times;
+ std::array<Common::FixedPoint<50, 14>, MaxDelayTaps> early_gains;
+ s32 pre_delay_time;
+ std::array<ReverbDelayLine, MaxDelayLines> decay_delay_lines;
+ std::array<ReverbDelayLine, MaxDelayLines> fdn_delay_lines;
+ std::array<Common::FixedPoint<50, 14>, MaxDelayLines> hf_decay_gain;
+ std::array<Common::FixedPoint<50, 14>, MaxDelayLines> hf_decay_prev_gain;
+ std::array<Common::FixedPoint<50, 14>, MaxDelayLines> prev_feedback_output;
+ };
+ static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
+ "ReverbInfo::State is too large!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/address_info.h b/src/audio_core/renderer/memory/address_info.h
new file mode 100644
index 000000000..bb5c930e1
--- /dev/null
+++ b/src/audio_core/renderer/memory/address_info.h
@@ -0,0 +1,124 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+/**
+ * Represents a region of mapped or unmapped memory.
+ */
+class AddressInfo {
+public:
+ AddressInfo() = default;
+ AddressInfo(CpuAddr cpu_address_, u64 size_) : cpu_address{cpu_address_}, size{size_} {}
+
+ /**
+ * Setup a new AddressInfo.
+ *
+ * @param cpu_address_ - The CPU address of this region.
+ * @param size_ - The size of this region.
+ */
+ void Setup(CpuAddr cpu_address_, u64 size_) {
+ cpu_address = cpu_address_;
+ size = size_;
+ memory_pool = nullptr;
+ dsp_address = 0;
+ }
+
+ /**
+ * Get the CPU address.
+ *
+ * @return The CpuAddr address
+ */
+ CpuAddr GetCpuAddr() const {
+ return cpu_address;
+ }
+
+ /**
+ * Assign this region to a memory pool.
+ *
+ * @param memory_pool_ - Memory pool to assign.
+ */
+ void SetPool(MemoryPoolInfo* memory_pool_) {
+ memory_pool = memory_pool_;
+ }
+
+ /**
+ * Get the size of this region.
+ *
+ * @return The size of this region.
+ */
+ u64 GetSize() const {
+ return size;
+ }
+
+ /**
+ * Get the ADSP address for this region.
+ *
+ * @return The ADSP address for this region.
+ */
+ CpuAddr GetForceMappedDspAddr() const {
+ return dsp_address;
+ }
+
+ /**
+ * Set the ADSP address for this region.
+ *
+ * @param dsp_addr - The new ADSP address for this region.
+ */
+ void SetForceMappedDspAddr(CpuAddr dsp_addr) {
+ dsp_address = dsp_addr;
+ }
+
+ /**
+ * Check whether this region has an active memory pool.
+ *
+ * @return True if this region has a mapped memory pool, otherwise false.
+ */
+ bool HasMappedMemoryPool() const {
+ return memory_pool != nullptr && memory_pool->GetDspAddress() != 0;
+ }
+
+ /**
+ * Check whether this region is mapped to the ADSP.
+ *
+ * @return True if this region is mapped, otherwise false.
+ */
+ bool IsMapped() const {
+ return HasMappedMemoryPool() || dsp_address != 0;
+ }
+
+ /**
+ * Get a usable reference to this region of memory.
+ *
+ * @param mark_in_use - Whether this region should be marked as being in use.
+ * @return A valid memory address if valid, otherwise 0.
+ */
+ CpuAddr GetReference(bool mark_in_use) {
+ if (!HasMappedMemoryPool()) {
+ return dsp_address;
+ }
+
+ if (mark_in_use) {
+ memory_pool->SetUsed(true);
+ }
+
+ return memory_pool->Translate(cpu_address, size);
+ }
+
+private:
+ /// CPU address of this region
+ CpuAddr cpu_address;
+ /// Size of this region
+ u64 size;
+ /// The memory this region is mapped to
+ MemoryPoolInfo* memory_pool;
+ /// ADSP address of this region
+ CpuAddr dsp_address;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/memory_pool_info.cpp b/src/audio_core/renderer/memory/memory_pool_info.cpp
new file mode 100644
index 000000000..9b7824af1
--- /dev/null
+++ b/src/audio_core/renderer/memory/memory_pool_info.cpp
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/memory/memory_pool_info.h"
+
+namespace AudioCore::AudioRenderer {
+
+CpuAddr MemoryPoolInfo::GetCpuAddress() const {
+ return cpu_address;
+}
+
+CpuAddr MemoryPoolInfo::GetDspAddress() const {
+ return dsp_address;
+}
+
+u64 MemoryPoolInfo::GetSize() const {
+ return size;
+}
+
+MemoryPoolInfo::Location MemoryPoolInfo::GetLocation() const {
+ return location;
+}
+
+void MemoryPoolInfo::SetCpuAddress(const CpuAddr address, const u64 size_) {
+ cpu_address = address;
+ size = size_;
+}
+
+void MemoryPoolInfo::SetDspAddress(const CpuAddr address) {
+ dsp_address = address;
+}
+
+bool MemoryPoolInfo::Contains(const CpuAddr address_, const u64 size_) const {
+ return cpu_address <= address_ && (address_ + size_) <= (cpu_address + size);
+}
+
+bool MemoryPoolInfo::IsMapped() const {
+ return dsp_address != 0;
+}
+
+CpuAddr MemoryPoolInfo::Translate(const CpuAddr address, const u64 size_) const {
+ if (!Contains(address, size_)) {
+ return 0;
+ }
+
+ if (!IsMapped()) {
+ return 0;
+ }
+
+ return dsp_address + (address - cpu_address);
+}
+
+void MemoryPoolInfo::SetUsed(const bool used) {
+ in_use = used;
+}
+
+bool MemoryPoolInfo::IsUsed() const {
+ return in_use;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/memory_pool_info.h b/src/audio_core/renderer/memory/memory_pool_info.h
new file mode 100644
index 000000000..537a466ec
--- /dev/null
+++ b/src/audio_core/renderer/memory/memory_pool_info.h
@@ -0,0 +1,170 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper).
+ */
+class MemoryPoolInfo {
+public:
+ /**
+ * The location of this pool.
+ * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper).
+ * DSP pools are mapped in the current process sysmodule.
+ */
+ enum class Location {
+ CPU = 1,
+ DSP = 2,
+ };
+
+ /**
+ * Current state of the pool
+ */
+ enum class State {
+ Invalid,
+ Aquired,
+ RequestDetach,
+ Detached,
+ RequestAttach,
+ Attached,
+ Released,
+ };
+
+ /**
+ * Result code for updating the pool (See InfoUpdater::Update)
+ */
+ enum class ResultState {
+ Success,
+ BadParam,
+ MapFailed,
+ InUse,
+ };
+
+ /**
+ * Input parameters coming from the game which are used to update current pools
+ * (See InfoUpdater::Update)
+ */
+ struct InParameter {
+ /* 0x00 */ u64 address;
+ /* 0x08 */ u64 size;
+ /* 0x10 */ State state;
+ /* 0x14 */ bool in_use;
+ /* 0x18 */ char unk18[0x8];
+ };
+ static_assert(sizeof(InParameter) == 0x20, "MemoryPoolInfo::InParameter has the wrong size!");
+
+ /**
+ * Output status sent back to the game on update (See InfoUpdater::Update)
+ */
+ struct OutStatus {
+ /* 0x00 */ State state;
+ /* 0x04 */ char unk04[0xC];
+ };
+ static_assert(sizeof(OutStatus) == 0x10, "MemoryPoolInfo::OutStatus has the wrong size!");
+
+ MemoryPoolInfo() = default;
+ MemoryPoolInfo(Location location_) : location{location_} {}
+
+ /**
+ * Get the CPU address for this pool.
+ *
+ * @return The CPU address of this pool.
+ */
+ CpuAddr GetCpuAddress() const;
+
+ /**
+ * Get the DSP address for this pool.
+ *
+ * @return The DSP address of this pool.
+ */
+ CpuAddr GetDspAddress() const;
+
+ /**
+ * Get the size of this pool.
+ *
+ * @return The size of this pool.
+ */
+ u64 GetSize() const;
+
+ /**
+ * Get the location of this pool.
+ *
+ * @return The location for the pool (see MemoryPoolInfo::Location).
+ */
+ Location GetLocation() const;
+
+ /**
+ * Set the CPU address for this pool.
+ *
+ * @param address - The new CPU address for this pool.
+ * @param size - The new size for this pool.
+ */
+ void SetCpuAddress(CpuAddr address, u64 size);
+
+ /**
+ * Set the DSP address for this pool.
+ *
+ * @param address - The new DSP address for this pool.
+ */
+ void SetDspAddress(CpuAddr address);
+
+ /**
+ * Check whether the pool contains a given range.
+ *
+ * @param address - The buffer address to look for.
+ * @param size - The size of the given buffer.
+ * @return True if the range is within this pool, otherwise false.
+ */
+ bool Contains(CpuAddr address, u64 size) const;
+
+ /**
+ * Check whether this pool is mapped, which is when the dsp address is set.
+ *
+ * @return True if the pool is mapped, otherwise false.
+ */
+ bool IsMapped() const;
+
+ /**
+ * Translates a given CPU range into a relative offset for the DSP.
+ *
+ * @param address - The buffer address to look for.
+ * @param size - The size of the given buffer.
+ * @return Pointer to the DSP-mapped memory.
+ */
+ CpuAddr Translate(CpuAddr address, u64 size) const;
+
+ /**
+ * Set or unset whether this memory pool is in use.
+ *
+ * @param used - Use state for this pool.
+ */
+ void SetUsed(bool used);
+
+ /**
+ * Get whether this pool is in use.
+ *
+ * @return True if in use, otherwise false.
+ */
+ bool IsUsed() const;
+
+private:
+ /// Base address for the CPU-side memory
+ CpuAddr cpu_address{};
+ /// Base address for the DSP-side memory
+ CpuAddr dsp_address{};
+ /// Size of this pool
+ u64 size{};
+ /// Location of this pool, either CPU or DSP
+ Location location{Location::DSP};
+ /// If this pool is in use
+ bool in_use{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/pool_mapper.cpp b/src/audio_core/renderer/memory/pool_mapper.cpp
new file mode 100644
index 000000000..2baf2ce08
--- /dev/null
+++ b/src/audio_core/renderer/memory/pool_mapper.cpp
@@ -0,0 +1,243 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/memory/address_info.h"
+#include "audio_core/renderer/memory/pool_mapper.h"
+#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/svc.h"
+
+namespace AudioCore::AudioRenderer {
+
+PoolMapper::PoolMapper(u32 process_handle_, bool force_map_)
+ : process_handle{process_handle_}, force_map{force_map_} {}
+
+PoolMapper::PoolMapper(u32 process_handle_, std::span<MemoryPoolInfo> pool_infos_, u32 pool_count_,
+ bool force_map_)
+ : process_handle{process_handle_}, pool_infos{pool_infos_.data()},
+ pool_count{pool_count_}, force_map{force_map_} {}
+
+void PoolMapper::ClearUseState(std::span<MemoryPoolInfo> pools, const u32 count) {
+ for (u32 i = 0; i < count; i++) {
+ pools[i].SetUsed(false);
+ }
+}
+
+MemoryPoolInfo* PoolMapper::FindMemoryPool(MemoryPoolInfo* pools, const u64 count,
+ const CpuAddr address, const u64 size) const {
+ auto pool{pools};
+ for (u64 i = 0; i < count; i++, pool++) {
+ if (pool->Contains(address, size)) {
+ return pool;
+ }
+ }
+ return nullptr;
+}
+
+MemoryPoolInfo* PoolMapper::FindMemoryPool(const CpuAddr address, const u64 size) const {
+ auto pool{pool_infos};
+ for (u64 i = 0; i < pool_count; i++, pool++) {
+ if (pool->Contains(address, size)) {
+ return pool;
+ }
+ }
+ return nullptr;
+}
+
+bool PoolMapper::FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools,
+ const u32 count) const {
+ if (address_info.GetCpuAddr() == 0) {
+ address_info.SetPool(nullptr);
+ return false;
+ }
+
+ auto found_pool{
+ FindMemoryPool(pools, count, address_info.GetCpuAddr(), address_info.GetSize())};
+ if (found_pool != nullptr) {
+ address_info.SetPool(found_pool);
+ return true;
+ }
+
+ if (force_map) {
+ address_info.SetForceMappedDspAddr(address_info.GetCpuAddr());
+ } else {
+ address_info.SetPool(nullptr);
+ }
+
+ return false;
+}
+
+bool PoolMapper::FillDspAddr(AddressInfo& address_info) const {
+ if (address_info.GetCpuAddr() == 0) {
+ address_info.SetPool(nullptr);
+ return false;
+ }
+
+ auto found_pool{FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())};
+ if (found_pool != nullptr) {
+ address_info.SetPool(found_pool);
+ return true;
+ }
+
+ if (force_map) {
+ address_info.SetForceMappedDspAddr(address_info.GetCpuAddr());
+ } else {
+ address_info.SetPool(nullptr);
+ }
+
+ return false;
+}
+
+bool PoolMapper::TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info,
+ const CpuAddr address, const u64 size) const {
+ address_info.Setup(address, size);
+
+ if (!FillDspAddr(address_info)) {
+ error_info.error_code = Service::Audio::ERR_POOL_MAPPING_FAILED;
+ error_info.address = address;
+ return force_map;
+ }
+
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ return true;
+}
+
+bool PoolMapper::IsForceMapEnabled() const {
+ return force_map;
+}
+
+u32 PoolMapper::GetProcessHandle(const MemoryPoolInfo* pool) const {
+ switch (pool->GetLocation()) {
+ case MemoryPoolInfo::Location::CPU:
+ return process_handle;
+ case MemoryPoolInfo::Location::DSP:
+ return Kernel::Svc::CurrentProcess;
+ }
+ LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location!");
+ return Kernel::Svc::CurrentProcess;
+}
+
+bool PoolMapper::Map([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr,
+ [[maybe_unused]] const u64 size) const {
+ // nn::audio::dsp::MapUserPointer(handle, cpu_addr, size);
+ return true;
+}
+
+bool PoolMapper::Map(MemoryPoolInfo& pool) const {
+ switch (pool.GetLocation()) {
+ case MemoryPoolInfo::Location::CPU:
+ // Map with process_handle
+ pool.SetDspAddress(pool.GetCpuAddress());
+ return true;
+ case MemoryPoolInfo::Location::DSP:
+ // Map with Kernel::Svc::CurrentProcess
+ pool.SetDspAddress(pool.GetCpuAddress());
+ return true;
+ default:
+ LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!",
+ static_cast<u32>(pool.GetLocation()));
+ return false;
+ }
+}
+
+bool PoolMapper::Unmap([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr,
+ [[maybe_unused]] const u64 size) const {
+ // nn::audio::dsp::UnmapUserPointer(handle, cpu_addr, size);
+ return true;
+}
+
+bool PoolMapper::Unmap(MemoryPoolInfo& pool) const {
+ [[maybe_unused]] u32 handle{0};
+
+ switch (pool.GetLocation()) {
+ case MemoryPoolInfo::Location::CPU:
+ handle = process_handle;
+ break;
+ case MemoryPoolInfo::Location::DSP:
+ handle = Kernel::Svc::CurrentProcess;
+ break;
+ }
+ // nn::audio::dsp::UnmapUserPointer(handle, pool->cpu_address, pool->size);
+ pool.SetCpuAddress(0, 0);
+ pool.SetDspAddress(0);
+ return true;
+}
+
+void PoolMapper::ForceUnmapPointer(const AddressInfo& address_info) const {
+ if (force_map) {
+ [[maybe_unused]] auto found_pool{
+ FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())};
+ // nn::audio::dsp::UnmapUserPointer(this->processHandle, address_info.GetCpuAddr(), 0);
+ }
+}
+
+MemoryPoolInfo::ResultState PoolMapper::Update(MemoryPoolInfo& pool,
+ const MemoryPoolInfo::InParameter& in_params,
+ MemoryPoolInfo::OutStatus& out_params) const {
+ if (in_params.state != MemoryPoolInfo::State::RequestAttach &&
+ in_params.state != MemoryPoolInfo::State::RequestDetach) {
+ return MemoryPoolInfo::ResultState::Success;
+ }
+
+ if (in_params.address == 0 || in_params.size == 0 || !Common::Is4KBAligned(in_params.address) ||
+ !Common::Is4KBAligned(in_params.size)) {
+ return MemoryPoolInfo::ResultState::BadParam;
+ }
+
+ switch (in_params.state) {
+ case MemoryPoolInfo::State::RequestAttach:
+ pool.SetCpuAddress(in_params.address, in_params.size);
+
+ Map(pool);
+
+ if (pool.IsMapped()) {
+ out_params.state = MemoryPoolInfo::State::Attached;
+ return MemoryPoolInfo::ResultState::Success;
+ }
+ pool.SetCpuAddress(0, 0);
+ return MemoryPoolInfo::ResultState::MapFailed;
+
+ case MemoryPoolInfo::State::RequestDetach:
+ if (pool.GetCpuAddress() != in_params.address || pool.GetSize() != in_params.size) {
+ return MemoryPoolInfo::ResultState::BadParam;
+ }
+
+ if (pool.IsUsed()) {
+ return MemoryPoolInfo::ResultState::InUse;
+ }
+
+ Unmap(pool);
+
+ pool.SetCpuAddress(0, 0);
+ pool.SetDspAddress(0);
+ out_params.state = MemoryPoolInfo::State::Detached;
+ return MemoryPoolInfo::ResultState::Success;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid MemoryPoolInfo::State!");
+ break;
+ }
+
+ return MemoryPoolInfo::ResultState::Success;
+}
+
+bool PoolMapper::InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory,
+ const u64 size_) const {
+ switch (pool.GetLocation()) {
+ case MemoryPoolInfo::Location::CPU:
+ return false;
+ case MemoryPoolInfo::Location::DSP:
+ pool.SetCpuAddress(reinterpret_cast<u64>(memory), size_);
+ if (Map(Kernel::Svc::CurrentProcess, reinterpret_cast<u64>(memory), size_)) {
+ pool.SetDspAddress(pool.GetCpuAddress());
+ return true;
+ }
+ return false;
+ default:
+ LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!",
+ static_cast<u32>(pool.GetLocation()));
+ return false;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/pool_mapper.h b/src/audio_core/renderer/memory/pool_mapper.h
new file mode 100644
index 000000000..9a691da7a
--- /dev/null
+++ b/src/audio_core/renderer/memory/pool_mapper.h
@@ -0,0 +1,179 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "common/common_types.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace AudioCore::AudioRenderer {
+class AddressInfo;
+
+/**
+ * Utility functions for managing MemoryPoolInfos
+ */
+class PoolMapper {
+public:
+ explicit PoolMapper(u32 process_handle, bool force_map);
+ explicit PoolMapper(u32 process_handle, std::span<MemoryPoolInfo> pool_infos, u32 pool_count,
+ bool force_map);
+
+ /**
+ * Clear the usage state for all given pools.
+ *
+ * @param pools - The memory pools to clear.
+ * @param count - The number of pools.
+ */
+ static void ClearUseState(std::span<MemoryPoolInfo> pools, u32 count);
+
+ /**
+ * Find the memory pool containing the given address and size from a given list of pools.
+ *
+ * @param pools - The memory pools to search within.
+ * @param count - The number of pools.
+ * @param address - The address of the region to find.
+ * @param size - The size of the region to find.
+ * @return Pointer to the memory pool if found, otherwise nullptr.
+ */
+ MemoryPoolInfo* FindMemoryPool(MemoryPoolInfo* pools, u64 count, CpuAddr address,
+ u64 size) const;
+
+ /**
+ * Find the memory pool containing the given address and size from the PoolMapper's memory pool.
+ *
+ * @param address - The address of the region to find.
+ * @param size - The size of the region to find.
+ * @return Pointer to the memory pool if found, otherwise nullptr.
+ */
+ MemoryPoolInfo* FindMemoryPool(CpuAddr address, u64 size) const;
+
+ /**
+ * Set the PoolMapper's memory pool to one in the given list of pools, which contains
+ * address_info.
+ *
+ * @param address_info - The expected region to find within pools.
+ * @param pools - The list of pools to search within.
+ * @param count - The number of pools given.
+ * @return True if successfully mapped, otherwise false.
+ */
+ bool FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools, u32 count) const;
+
+ /**
+ * Set the PoolMapper's memory pool to the one containing address_info.
+ *
+ * @param address_info - The address to find the memory pool for.
+ * @return True if successfully mapped, otherwise false.
+ */
+ bool FillDspAddr(AddressInfo& address_info) const;
+
+ /**
+ * Try to attach a {address, size} region to the given address_info, and map it. Fills in the
+ * given error_info and address_info.
+ *
+ * @param error_info - Output error info.
+ * @param address_info - Output address info, initialized with the given {address, size} and
+ * attempted to map.
+ * @param address - Address of the region to map.
+ * @param size - Size of the region to map.
+ * @return True if successfully attached, otherwise false.
+ */
+ bool TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info,
+ CpuAddr address, u64 size) const;
+
+ /**
+ * Return whether force mapping is enabled.
+ *
+ * @return True if force mapping is enabled, otherwise false.
+ */
+ bool IsForceMapEnabled() const;
+
+ /**
+ * Get the process handle, depending on location.
+ *
+ * @param pool - The pool to check the location of.
+ * @return CurrentProcessHandle if location == DSP,
+ * the PoolMapper's process_handle if location == CPU
+ */
+ u32 GetProcessHandle(const MemoryPoolInfo* pool) const;
+
+ /**
+ * Map the given region with the given handle. This is a no-op.
+ *
+ * @param handle - The process handle to map to.
+ * @param cpu_addr - Address to map.
+ * @param size - Size to map.
+ * @return True if successfully mapped, otherwise false.
+ */
+ bool Map(u32 handle, CpuAddr cpu_addr, u64 size) const;
+
+ /**
+ * Map the given memory pool.
+ *
+ * @param pool - The pool to map.
+ * @return True if successfully mapped, otherwise false.
+ */
+ bool Map(MemoryPoolInfo& pool) const;
+
+ /**
+ * Unmap the given region with the given handle.
+ *
+ * @param handle - The process handle to unmap to.
+ * @param cpu_addr - Address to unmap.
+ * @param size - Size to unmap.
+ * @return True if successfully unmapped, otherwise false.
+ */
+ bool Unmap(u32 handle, CpuAddr cpu_addr, u64 size) const;
+
+ /**
+ * Unmap the given memory pool.
+ *
+ * @param pool - The pool to unmap.
+ * @return True if successfully unmapped, otherwise false.
+ */
+ bool Unmap(MemoryPoolInfo& pool) const;
+
+ /**
+ * Forcibly unmap the given region.
+ *
+ * @param address_info - The region to unmap.
+ */
+ void ForceUnmapPointer(const AddressInfo& address_info) const;
+
+ /**
+ * Update the given memory pool.
+ *
+ * @param pool - Pool to update.
+ * @param in_params - Input parameters for the update.
+ * @param out_params - Output parameters for the update.
+ * @return The result of the update. See MemoryPoolInfo::ResultState
+ */
+ MemoryPoolInfo::ResultState Update(MemoryPoolInfo& pool,
+ const MemoryPoolInfo::InParameter& in_params,
+ MemoryPoolInfo::OutStatus& out_params) const;
+
+ /**
+ * Initialize the PoolMapper's memory pool.
+ *
+ * @param pool - Input pool to initialize.
+ * @param memory - Pointer to the memory region for the pool.
+ * @param size - Size of the memory region for the pool.
+ * @return True if initialized successfully, otherwise false.
+ */
+ bool InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory, u64 size) const;
+
+private:
+ /// Process handle for this mapper, used when location == CPU
+ u32 process_handle;
+ /// List of memory pools assigned to this mapper
+ MemoryPoolInfo* pool_infos{};
+ /// The number of pools
+ u64 pool_count{};
+ /// Is forced mapping enabled
+ bool force_map;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/mix/mix_context.cpp b/src/audio_core/renderer/mix/mix_context.cpp
new file mode 100644
index 000000000..2427c83ed
--- /dev/null
+++ b/src/audio_core/renderer/mix/mix_context.cpp
@@ -0,0 +1,141 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <ranges>
+
+#include "audio_core/renderer/mix/mix_context.h"
+#include "audio_core/renderer/splitter/splitter_context.h"
+
+namespace AudioCore::AudioRenderer {
+
+void MixContext::Initialize(std::span<MixInfo*> sorted_mix_infos_, std::span<MixInfo> mix_infos_,
+ const u32 count_, std::span<s32> effect_process_order_buffer_,
+ const u32 effect_count_, std::span<u8> node_states_workbuffer,
+ const u64 node_buffer_size, std::span<u8> edge_matrix_workbuffer,
+ const u64 edge_matrix_size) {
+ count = count_;
+ sorted_mix_infos = sorted_mix_infos_;
+ mix_infos = mix_infos_;
+ effect_process_order_buffer = effect_process_order_buffer_;
+ effect_count = effect_count_;
+
+ if (node_states_workbuffer.size() > 0 && edge_matrix_workbuffer.size() > 0) {
+ node_states.Initialize(node_states_workbuffer, node_buffer_size, count);
+ edge_matrix.Initialize(edge_matrix_workbuffer, edge_matrix_size, count);
+ }
+
+ for (s32 i = 0; i < count; i++) {
+ sorted_mix_infos[i] = &mix_infos[i];
+ }
+}
+
+MixInfo* MixContext::GetSortedInfo(const s32 index) {
+ return sorted_mix_infos[index];
+}
+
+void MixContext::SetSortedInfo(const s32 index, MixInfo& mix_info) {
+ sorted_mix_infos[index] = &mix_info;
+}
+
+MixInfo* MixContext::GetInfo(const s32 index) {
+ return &mix_infos[index];
+}
+
+MixInfo* MixContext::GetFinalMixInfo() {
+ return &mix_infos[0];
+}
+
+s32 MixContext::GetCount() const {
+ return count;
+}
+
+void MixContext::UpdateDistancesFromFinalMix() {
+ for (s32 i = 0; i < count; i++) {
+ mix_infos[i].distance_from_final_mix = InvalidDistanceFromFinalMix;
+ }
+
+ for (s32 i = 0; i < count; i++) {
+ auto& mix_info{mix_infos[i]};
+ sorted_mix_infos[i] = &mix_info;
+
+ if (!mix_info.in_use) {
+ continue;
+ }
+
+ auto mix_id{mix_info.mix_id};
+ auto distance_to_final_mix{FinalMixId};
+
+ while (distance_to_final_mix < count) {
+ if (mix_id == FinalMixId) {
+ break;
+ }
+
+ if (mix_id == UnusedMixId) {
+ distance_to_final_mix = InvalidDistanceFromFinalMix;
+ break;
+ }
+
+ auto distance_from_final_mix{mix_infos[mix_id].distance_from_final_mix};
+ if (distance_from_final_mix != InvalidDistanceFromFinalMix) {
+ distance_to_final_mix = distance_from_final_mix + 1;
+ break;
+ }
+
+ distance_to_final_mix++;
+ mix_id = mix_infos[mix_id].dst_mix_id;
+ }
+
+ if (distance_to_final_mix >= count) {
+ distance_to_final_mix = InvalidDistanceFromFinalMix;
+ }
+ mix_info.distance_from_final_mix = distance_to_final_mix;
+ }
+}
+
+void MixContext::SortInfo() {
+ UpdateDistancesFromFinalMix();
+
+ std::ranges::sort(sorted_mix_infos, [](const MixInfo* lhs, const MixInfo* rhs) {
+ return lhs->distance_from_final_mix > rhs->distance_from_final_mix;
+ });
+
+ CalcMixBufferOffset();
+}
+
+void MixContext::CalcMixBufferOffset() {
+ s16 offset{0};
+ for (s32 i = 0; i < count; i++) {
+ auto mix_info{sorted_mix_infos[i]};
+ if (mix_info->in_use) {
+ const auto buffer_count{mix_info->buffer_count};
+ mix_info->buffer_offset = offset;
+ offset += buffer_count;
+ }
+ }
+}
+
+bool MixContext::TSortInfo(const SplitterContext& splitter_context) {
+ if (!splitter_context.UsingSplitter()) {
+ CalcMixBufferOffset();
+ return true;
+ }
+
+ if (!node_states.Tsort(edge_matrix)) {
+ return false;
+ }
+
+ std::vector<s32> sorted_results{node_states.GetSortedResuls()};
+ const auto result_size{std::min(count, static_cast<s32>(sorted_results.size()))};
+ for (s32 i = 0; i < result_size; i++) {
+ sorted_mix_infos[i] = &mix_infos[sorted_results[i]];
+ }
+
+ CalcMixBufferOffset();
+ return true;
+}
+
+EdgeMatrix& MixContext::GetEdgeMatrix() {
+ return edge_matrix;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/mix/mix_context.h b/src/audio_core/renderer/mix/mix_context.h
new file mode 100644
index 000000000..da3aa2829
--- /dev/null
+++ b/src/audio_core/renderer/mix/mix_context.h
@@ -0,0 +1,124 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/mix/mix_info.h"
+#include "audio_core/renderer/nodes/edge_matrix.h"
+#include "audio_core/renderer/nodes/node_states.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class SplitterContext;
+
+/*
+ * Manages mixing states, sorting and building a node graph to describe a mix order.
+ */
+class MixContext {
+public:
+ /**
+ * Initialize the mix context.
+ *
+ * @param sorted_mix_infos - Buffer for the sorted mix infos.
+ * @param mix_infos - Buffer for the mix infos.
+ * @param effect_process_order_buffer - Buffer for the effect process orders.
+ * @param effect_count - Number of effects in the buffer.
+ * @param node_states_workbuffer - Buffer for node states.
+ * @param node_buffer_size - Size of the node states buffer.
+ * @param edge_matrix_workbuffer - Buffer for edge matrix.
+ * @param edge_matrix_size - Size of the edge matrix buffer.
+ */
+ void Initialize(std::span<MixInfo*> sorted_mix_infos, std::span<MixInfo> mix_infos, u32 count_,
+ std::span<s32> effect_process_order_buffer, u32 effect_count,
+ std::span<u8> node_states_workbuffer, u64 node_buffer_size,
+ std::span<u8> edge_matrix_workbuffer, u64 edge_matrix_size);
+
+ /**
+ * Get a sorted mix at the given index.
+ *
+ * @param index - Index of sorted mix.
+ * @return The sorted mix.
+ */
+ MixInfo* GetSortedInfo(s32 index);
+
+ /**
+ * Set the sorted info at the given index.
+ *
+ * @param index - Index of sorted mix.
+ * @param mix_info - The new mix for this index.
+ */
+ void SetSortedInfo(s32 index, MixInfo& mix_info);
+
+ /**
+ * Get a mix at the given index.
+ *
+ * @param index - Index of mix.
+ * @return The mix.
+ */
+ MixInfo* GetInfo(s32 index);
+
+ /**
+ * Get the final mix.
+ *
+ * @return The final mix.
+ */
+ MixInfo* GetFinalMixInfo();
+
+ /**
+ * Get the current number of mixes.
+ *
+ * @return The number of active mixes.
+ */
+ s32 GetCount() const;
+
+ /**
+ * Update all of the mixes' distance from the final mix.
+ * Needs to be called after altering the mix graph.
+ */
+ void UpdateDistancesFromFinalMix();
+
+ /**
+ * Non-splitter sort, sorts the sorted mixes based on their distance from the final mix.
+ */
+ void SortInfo();
+
+ /**
+ * Re-calculate the mix buffer offsets for each mix after altering the mix.
+ */
+ void CalcMixBufferOffset();
+
+ /**
+ * Splitter sort, traverse the splitter node graph and sort the sorted mixes from results.
+ *
+ * @param splitter_context - Splitter context for the sort.
+ * @return True if the sort was successful, othewise false.
+ */
+ bool TSortInfo(const SplitterContext& splitter_context);
+
+ /**
+ * Get the edge matrix used for the mix graph.
+ *
+ * @return The edge matrix used.
+ */
+ EdgeMatrix& GetEdgeMatrix();
+
+private:
+ /// Array of sorted mixes
+ std::span<MixInfo*> sorted_mix_infos{};
+ /// Array of mixes
+ std::span<MixInfo> mix_infos{};
+ /// Number of active mixes
+ s32 count{};
+ /// Array of effect process orderings
+ std::span<s32> effect_process_order_buffer{};
+ /// Number of effects in the process ordering buffer
+ u64 effect_count{};
+ /// Node states used in splitter sort
+ NodeStates node_states{};
+ /// Edge matrix for connected nodes used in splitter sort
+ EdgeMatrix edge_matrix{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/mix/mix_info.cpp b/src/audio_core/renderer/mix/mix_info.cpp
new file mode 100644
index 000000000..cc18e57ee
--- /dev/null
+++ b/src/audio_core/renderer/mix/mix_info.cpp
@@ -0,0 +1,120 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/effect/effect_context.h"
+#include "audio_core/renderer/mix/mix_info.h"
+#include "audio_core/renderer/nodes/edge_matrix.h"
+#include "audio_core/renderer/splitter/splitter_context.h"
+
+namespace AudioCore::AudioRenderer {
+
+MixInfo::MixInfo(std::span<s32> effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior)
+ : effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_},
+ long_size_pre_delay_supported{behavior.IsLongSizePreDelaySupported()} {
+ ClearEffectProcessingOrder();
+}
+
+void MixInfo::Cleanup() {
+ mix_id = UnusedMixId;
+ dst_mix_id = UnusedMixId;
+ dst_splitter_id = UnusedSplitterId;
+}
+
+void MixInfo::ClearEffectProcessingOrder() {
+ for (s32 i = 0; i < effect_count; i++) {
+ effect_order_buffer[i] = -1;
+ }
+}
+
+bool MixInfo::Update(EdgeMatrix& edge_matrix, const InParameter& in_params,
+ EffectContext& effect_context, SplitterContext& splitter_context,
+ const BehaviorInfo& behavior) {
+ volume = in_params.volume;
+ sample_rate = in_params.sample_rate;
+ buffer_count = static_cast<s16>(in_params.buffer_count);
+ in_use = in_params.in_use;
+ mix_id = in_params.mix_id;
+ node_id = in_params.node_id;
+ mix_volumes = in_params.mix_volumes;
+
+ bool sort_required{false};
+ if (behavior.IsSplitterSupported()) {
+ sort_required = UpdateConnection(edge_matrix, in_params, splitter_context);
+ } else {
+ if (dst_mix_id != in_params.dest_mix_id) {
+ dst_mix_id = in_params.dest_mix_id;
+ sort_required = true;
+ }
+ dst_splitter_id = UnusedSplitterId;
+ }
+
+ ClearEffectProcessingOrder();
+
+ // Check all effects, and set their order if they belong to this mix.
+ const auto count{effect_context.GetCount()};
+ for (u32 i = 0; i < count; i++) {
+ const auto& info{effect_context.GetInfo(i)};
+ if (mix_id == info.GetMixId()) {
+ const auto processing_order{info.GetProcessingOrder()};
+ if (processing_order > effect_count) {
+ break;
+ }
+ effect_order_buffer[processing_order] = i;
+ }
+ }
+
+ return sort_required;
+}
+
+bool MixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params,
+ SplitterContext& splitter_context) {
+ auto has_new_connection{false};
+ if (dst_splitter_id != UnusedSplitterId) {
+ auto& splitter_info{splitter_context.GetInfo(dst_splitter_id)};
+ has_new_connection = splitter_info.HasNewConnection();
+ }
+
+ // Check if this mix matches the input parameters.
+ // If everything is the same, don't bother updating.
+ if (dst_mix_id == in_params.dest_mix_id && dst_splitter_id == in_params.dest_splitter_id &&
+ !has_new_connection) {
+ return false;
+ }
+
+ // Reset the mix in the graph, as we're about to update it.
+ edge_matrix.RemoveEdges(mix_id);
+
+ if (in_params.dest_mix_id == UnusedMixId) {
+ if (in_params.dest_splitter_id != UnusedSplitterId) {
+ // If the splitter is used, connect this mix to each active destination.
+ auto& splitter_info{splitter_context.GetInfo(in_params.dest_splitter_id)};
+ auto const destination_count{splitter_info.GetDestinationCount()};
+
+ for (u32 i = 0; i < destination_count; i++) {
+ auto destination{
+ splitter_context.GetDesintationData(in_params.dest_splitter_id, i)};
+
+ if (destination) {
+ const auto destination_id{destination->GetMixId()};
+ if (destination_id != UnusedMixId) {
+ edge_matrix.Connect(mix_id, destination_id);
+ }
+ }
+ }
+ }
+ } else {
+ // If the splitter is not used, only connect this mix to its destination.
+ edge_matrix.Connect(mix_id, in_params.dest_mix_id);
+ }
+
+ dst_mix_id = in_params.dest_mix_id;
+ dst_splitter_id = in_params.dest_splitter_id;
+ return true;
+}
+
+bool MixInfo::HasAnyConnection() const {
+ return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/mix/mix_info.h b/src/audio_core/renderer/mix/mix_info.h
new file mode 100644
index 000000000..b5fa4c0c7
--- /dev/null
+++ b/src/audio_core/renderer/mix/mix_info.h
@@ -0,0 +1,124 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class EdgeMatrix;
+class SplitterContext;
+class EffectContext;
+class BehaviorInfo;
+
+/**
+ * A single mix, which may feed through other mixes in a chain until reaching the final output mix.
+ */
+class MixInfo {
+public:
+ struct InParameter {
+ /* 0x000 */ f32 volume;
+ /* 0x004 */ u32 sample_rate;
+ /* 0x008 */ u32 buffer_count;
+ /* 0x00C */ bool in_use;
+ /* 0x00D */ bool is_dirty;
+ /* 0x010 */ s32 mix_id;
+ /* 0x014 */ u32 effect_count;
+ /* 0x018 */ s32 node_id;
+ /* 0x01C */ char unk01C[0x8];
+ /* 0x024 */ std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes;
+ /* 0x924 */ s32 dest_mix_id;
+ /* 0x928 */ s32 dest_splitter_id;
+ /* 0x92C */ char unk92C[0x4];
+ };
+ static_assert(sizeof(InParameter) == 0x930, "MixInfo::InParameter has the wrong size!");
+
+ struct InDirtyParameter {
+ /* 0x00 */ u32 magic;
+ /* 0x04 */ s32 count;
+ /* 0x08 */ char unk08[0x18];
+ };
+ static_assert(sizeof(InDirtyParameter) == 0x20,
+ "MixInfo::InDirtyParameter has the wrong size!");
+
+ MixInfo(std::span<s32> effect_order_buffer, s32 effect_count, BehaviorInfo& behavior);
+
+ /**
+ * Clean up the mix, resetting it to a default state.
+ */
+ void Cleanup();
+
+ /**
+ * Clear the effect process order for all effects in this mix.
+ */
+ void ClearEffectProcessingOrder();
+
+ /**
+ * Update the mix according to the given parameters.
+ *
+ * @param edge_matrix - Updated with new splitter node connections, if supported.
+ * @param in_params - Input parameters.
+ * @param effect_context - Used to update the effect orderings.
+ * @param splitter_context - Used to update the mix graph if supported.
+ * @param behavior - Used for checking which features are supported.
+ * @return True if the mix was updated and a sort is required, otherwise false.
+ */
+ bool Update(EdgeMatrix& edge_matrix, const InParameter& in_params,
+ EffectContext& effect_context, SplitterContext& splitter_context,
+ const BehaviorInfo& behavior);
+
+ /**
+ * Update the mix's connection in the node graph according to the given parameters.
+ *
+ * @param edge_matrix - Updated with new splitter node connections, if supported.
+ * @param in_params - Input parameters.
+ * @param splitter_context - Used to update the mix graph if supported.
+ * @return True if the mix was updated and a sort is required, otherwise false.
+ */
+ bool UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params,
+ SplitterContext& splitter_context);
+
+ /**
+ * Check if this mix is connected to any other.
+ *
+ * @return True if the mix has a connection, otherwise false.
+ */
+ bool HasAnyConnection() const;
+
+ /// Volume of this mix
+ f32 volume{};
+ /// Sample rate of this mix
+ u32 sample_rate{};
+ /// Number of buffers in this mix
+ s16 buffer_count{};
+ /// Is this mix in use?
+ bool in_use{};
+ /// Is this mix enabled?
+ bool enabled{};
+ /// Id of this mix
+ s32 mix_id{UnusedMixId};
+ /// Node id of this mix
+ s32 node_id{};
+ /// Buffer offset for this mix
+ s16 buffer_offset{};
+ /// Distance to the final mix
+ s32 distance_from_final_mix{InvalidDistanceFromFinalMix};
+ /// Array of effect orderings of all effects in this mix
+ std::span<s32> effect_order_buffer;
+ /// Number of effects in this mix
+ const s32 effect_count;
+ /// Id for next mix in the chain
+ s32 dst_mix_id{UnusedMixId};
+ /// Mixing volumes for this mix used when this mix is chained with another
+ std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes{};
+ /// Id for next mix in the graph when splitter is used
+ s32 dst_splitter_id{UnusedSplitterId};
+ /// Is a longer pre-delay time supported for the reverb effect?
+ const bool long_size_pre_delay_supported;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/bit_array.h b/src/audio_core/renderer/nodes/bit_array.h
new file mode 100644
index 000000000..b0d53cd51
--- /dev/null
+++ b/src/audio_core/renderer/nodes/bit_array.h
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Represents an array of bits used for nodes and edges for the mixing graph.
+ */
+struct BitArray {
+ void reset() {
+ buffer.assign(buffer.size(), false);
+ }
+
+ /// Bits
+ std::vector<bool> buffer{};
+ /// Size of the buffer
+ u32 size{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/edge_matrix.cpp b/src/audio_core/renderer/nodes/edge_matrix.cpp
new file mode 100644
index 000000000..5573f33b9
--- /dev/null
+++ b/src/audio_core/renderer/nodes/edge_matrix.cpp
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/nodes/edge_matrix.h"
+
+namespace AudioCore::AudioRenderer {
+
+void EdgeMatrix::Initialize([[maybe_unused]] std::span<u8> buffer,
+ [[maybe_unused]] const u64 node_buffer_size, const u32 count_) {
+ count = count_;
+ edges.buffer.resize(count_ * count_);
+ edges.size = count_ * count_;
+ edges.reset();
+}
+
+bool EdgeMatrix::Connected(const u32 id, const u32 destination_id) const {
+ return edges.buffer[count * id + destination_id];
+}
+
+void EdgeMatrix::Connect(const u32 id, const u32 destination_id) {
+ edges.buffer[count * id + destination_id] = true;
+}
+
+void EdgeMatrix::Disconnect(const u32 id, const u32 destination_id) {
+ edges.buffer[count * id + destination_id] = false;
+}
+
+void EdgeMatrix::RemoveEdges(const u32 id) {
+ for (u32 dest = 0; dest < count; dest++) {
+ Disconnect(id, dest);
+ }
+}
+
+u32 EdgeMatrix::GetNodeCount() const {
+ return count;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/edge_matrix.h b/src/audio_core/renderer/nodes/edge_matrix.h
new file mode 100644
index 000000000..27a20e43e
--- /dev/null
+++ b/src/audio_core/renderer/nodes/edge_matrix.h
@@ -0,0 +1,82 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/nodes/bit_array.h"
+#include "common/alignment.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * An edge matrix, holding the connections for each node to every other node in the graph.
+ */
+class EdgeMatrix {
+public:
+ /**
+ * Calculate the size required for its workbuffer.
+ *
+ * @param count - The number of nodes in the graph.
+ * @return The required workbuffer size.
+ */
+ static u64 GetWorkBufferSize(u32 count) {
+ return Common::AlignUp(count * count, 0x40) / sizeof(u64);
+ }
+
+ /**
+ * Initialize this edge matrix.
+ *
+ * @param buffer - The workbuffer to use. Unused.
+ * @param node_buffer_size - The size of the workbuffer. Unused.
+ * @param count - The number of nodes in the graph.
+ */
+ void Initialize(std::span<u8> buffer, u64 node_buffer_size, u32 count);
+
+ /**
+ * Check if a node is connected to another.
+ *
+ * @param id - The node id to check.
+ * @param destination_id - Node id to check connection with.
+ */
+ bool Connected(u32 id, u32 destination_id) const;
+
+ /**
+ * Connect a node to another.
+ *
+ * @param id - The node id to connect.
+ * @param destination_id - Destination to connect it to.
+ */
+ void Connect(u32 id, u32 destination_id);
+
+ /**
+ * Disconnect a node from another.
+ *
+ * @param id - The node id to disconnect.
+ * @param destination_id - Destination to disconnect it from.
+ */
+ void Disconnect(u32 id, u32 destination_id);
+
+ /**
+ * Remove all connections for a given node.
+ *
+ * @param id - The node id to disconnect.
+ */
+ void RemoveEdges(u32 id);
+
+ /**
+ * Get the number of nodes in the graph.
+ *
+ * @return Number of nodes.
+ */
+ u32 GetNodeCount() const;
+
+private:
+ /// Edges for the current graph
+ BitArray edges;
+ /// Number of nodes (not edges) in the graph
+ u32 count;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/node_states.cpp b/src/audio_core/renderer/nodes/node_states.cpp
new file mode 100644
index 000000000..1821a51e6
--- /dev/null
+++ b/src/audio_core/renderer/nodes/node_states.cpp
@@ -0,0 +1,141 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/nodes/node_states.h"
+#include "common/logging/log.h"
+
+namespace AudioCore::AudioRenderer {
+
+void NodeStates::Initialize(std::span<u8> buffer_, [[maybe_unused]] const u64 node_buffer_size,
+ const u32 count) {
+ u64 num_blocks{Common::AlignUp(count, 0x40) / sizeof(u64)};
+ u64 offset{0};
+
+ node_count = count;
+
+ nodes_found.buffer.resize(count);
+ nodes_found.size = count;
+ nodes_found.reset();
+
+ offset += num_blocks;
+
+ nodes_complete.buffer.resize(count);
+ nodes_complete.size = count;
+ nodes_complete.reset();
+
+ offset += num_blocks;
+
+ results = {reinterpret_cast<u32*>(&buffer_[offset]), count};
+
+ offset += count * sizeof(u32);
+
+ stack.stack = {reinterpret_cast<u32*>(&buffer_[offset]), count * count};
+ stack.size = count * count;
+ stack.unk_10 = count * count;
+
+ offset += count * count * sizeof(u32);
+}
+
+bool NodeStates::Tsort(const EdgeMatrix& edge_matrix) {
+ return DepthFirstSearch(edge_matrix, stack);
+}
+
+bool NodeStates::DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack_) {
+ ResetState();
+
+ for (u32 node_id = 0; node_id < node_count; node_id++) {
+ if (GetState(node_id) == SearchState::Unknown) {
+ stack_.push(node_id);
+ }
+
+ while (stack_.Count() > 0) {
+ auto current_node{stack_.top()};
+ switch (GetState(current_node)) {
+ case SearchState::Unknown:
+ SetState(current_node, SearchState::Found);
+ break;
+ case SearchState::Found:
+ SetState(current_node, SearchState::Complete);
+ PushTsortResult(current_node);
+ stack_.pop();
+ continue;
+ case SearchState::Complete:
+ stack_.pop();
+ continue;
+ }
+
+ const auto edge_count{edge_matrix.GetNodeCount()};
+ for (u32 edge_id = 0; edge_id < edge_count; edge_id++) {
+ if (!edge_matrix.Connected(current_node, edge_id)) {
+ continue;
+ }
+
+ switch (GetState(edge_id)) {
+ case SearchState::Unknown:
+ stack_.push(edge_id);
+ break;
+ case SearchState::Found:
+ LOG_ERROR(Service_Audio,
+ "Cycle detected in the node graph, graph is not a DAG! "
+ "Bailing to avoid an infinite loop");
+ ResetState();
+ return false;
+ case SearchState::Complete:
+ break;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+NodeStates::SearchState NodeStates::GetState(const u32 id) const {
+ if (nodes_found.buffer[id]) {
+ return SearchState::Found;
+ } else if (nodes_complete.buffer[id]) {
+ return SearchState::Complete;
+ }
+ return SearchState::Unknown;
+}
+
+void NodeStates::PushTsortResult(const u32 id) {
+ results[result_pos++] = id;
+}
+
+void NodeStates::SetState(const u32 id, const SearchState state) {
+ switch (state) {
+ case SearchState::Complete:
+ nodes_found.buffer[id] = false;
+ nodes_complete.buffer[id] = true;
+ break;
+ case SearchState::Found:
+ nodes_found.buffer[id] = true;
+ nodes_complete.buffer[id] = false;
+ break;
+ case SearchState::Unknown:
+ nodes_found.buffer[id] = false;
+ nodes_complete.buffer[id] = false;
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Unknown node SearchState {}", static_cast<u32>(state));
+ break;
+ }
+}
+
+void NodeStates::ResetState() {
+ nodes_found.reset();
+ nodes_complete.reset();
+ std::fill(results.begin(), results.end(), -1);
+ result_pos = 0;
+}
+
+u32 NodeStates::GetNodeCount() const {
+ return node_count;
+}
+
+std::vector<s32> NodeStates::GetSortedResuls() const {
+ return {results.rbegin(), results.rbegin() + result_pos};
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h
new file mode 100644
index 000000000..94b1d1254
--- /dev/null
+++ b/src/audio_core/renderer/nodes/node_states.h
@@ -0,0 +1,195 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+#include <vector>
+
+#include "audio_core/renderer/nodes/edge_matrix.h"
+#include "common/alignment.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Graph utility functions for sorting and getting results from the DAG.
+ */
+class NodeStates {
+ /**
+ * State of a node in the depth first search.
+ */
+ enum class SearchState {
+ Unknown,
+ Found,
+ Complete,
+ };
+
+ /**
+ * Stack used for a depth first search.
+ */
+ struct Stack {
+ /**
+ * Calculate the workbuffer size required for this stack.
+ *
+ * @param count - Maximum number of nodes for the stack.
+ * @return Required buffer size.
+ */
+ static u32 CalcBufferSize(u32 count) {
+ return count * sizeof(u32);
+ }
+
+ /**
+ * Reset the stack back to default.
+ *
+ * @param buffer_ - The new buffer to use.
+ * @param size_ - The size of the new buffer.
+ */
+ void Reset(u32* buffer_, u32 size_) {
+ stack = {buffer_, size_};
+ size = size_;
+ pos = 0;
+ unk_10 = size_;
+ }
+
+ /**
+ * Get the current stack position.
+ *
+ * @return The current stack position.
+ */
+ u32 Count() const {
+ return pos;
+ }
+
+ /**
+ * Push a new node to the stack.
+ *
+ * @param data - The node to push.
+ */
+ void push(u32 data) {
+ stack[pos++] = data;
+ }
+
+ /**
+ * Pop a node from the stack.
+ *
+ * @return The node on the top of the stack.
+ */
+ u32 pop() {
+ return stack[--pos];
+ }
+
+ /**
+ * Get the top of the stack without popping.
+ *
+ * @return The node on the top of the stack.
+ */
+ u32 top() const {
+ return stack[pos - 1];
+ }
+
+ /// Buffer for the stack
+ std::span<u32> stack{};
+ /// Size of the stack buffer
+ u32 size{};
+ /// Current stack position
+ u32 pos{};
+ /// Unknown
+ u32 unk_10{};
+ };
+
+public:
+ /**
+ * Calculate the workbuffer size required for the node states.
+ *
+ * @param count - The number of nodes.
+ * @return The required workbuffer size.
+ */
+ static u64 GetWorkBufferSize(u32 count) {
+ return (Common::AlignUp(count, 0x40) / sizeof(u64)) * 2 + count * sizeof(BitArray) +
+ count * Stack::CalcBufferSize(count);
+ }
+
+ /**
+ * Initialize the node states.
+ *
+ * @param buffer_ - The workbuffer to use. Unused.
+ * @param node_buffer_size - The size of the workbuffer. Unused.
+ * @param count - The number of nodes in the graph.
+ */
+ void Initialize(std::span<u8> buffer_, u64 node_buffer_size, u32 count);
+
+ /**
+ * Sort the graph. Only calls DepthFirstSearch.
+ *
+ * @param edge_matrix - The edge matrix used to hold the connections between nodes.
+ * @return True if the sort was successful, otherwise false.
+ */
+ bool Tsort(const EdgeMatrix& edge_matrix);
+
+ /**
+ * Sort the graph via depth first search.
+ *
+ * @param edge_matrix - The edge matrix used to hold the connections between nodes.
+ * @param stack - The stack used for pushing and popping nodes.
+ * @return True if the sort was successful, otherwise false.
+ */
+ bool DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack);
+
+ /**
+ * Get the search state of a given node.
+ *
+ * @param id - The node id to check.
+ * @return The node's search state. See SearchState
+ */
+ SearchState GetState(u32 id) const;
+
+ /**
+ * Push a node id to the results buffer when found in the DFS.
+ *
+ * @param id - The node id to push.
+ */
+ void PushTsortResult(u32 id);
+
+ /**
+ * Set the state of a node.
+ *
+ * @param id - The node id to alter.
+ * @param state - The new search state.
+ */
+ void SetState(u32 id, SearchState state);
+
+ /**
+ * Reset the nodes found, complete and the results.
+ */
+ void ResetState();
+
+ /**
+ * Get the number of nodes in the graph.
+ *
+ * @return The number of nodes.
+ */
+ u32 GetNodeCount() const;
+
+ /**
+ * Get the sorted results from the DFS.
+ *
+ * @return Vector of nodes in reverse order.
+ */
+ std::vector<s32> GetSortedResuls() const;
+
+private:
+ /// Number of nodes in the graph
+ u32 node_count{};
+ /// Position in results buffer
+ u32 result_pos{};
+ /// List of nodes found
+ BitArray nodes_found{};
+ /// List of nodes completed
+ BitArray nodes_complete{};
+ /// List of results from the depth first search
+ std::span<u32> results{};
+ /// Stack used during the depth first search
+ Stack stack{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/detail_aspect.cpp b/src/audio_core/renderer/performance/detail_aspect.cpp
new file mode 100644
index 000000000..f6405937f
--- /dev/null
+++ b/src/audio_core/renderer/performance/detail_aspect.cpp
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/command/command_buffer.h"
+#include "audio_core/renderer/command/command_generator.h"
+#include "audio_core/renderer/performance/detail_aspect.h"
+
+namespace AudioCore::AudioRenderer {
+
+DetailAspect::DetailAspect(CommandGenerator& command_generator_,
+ const PerformanceEntryType entry_type, const s32 node_id_,
+ const PerformanceDetailType detail_type)
+ : command_generator{command_generator_}, node_id{node_id_} {
+ auto perf_manager{command_generator.GetPerformanceManager()};
+ if (perf_manager != nullptr && perf_manager->IsInitialized() &&
+ perf_manager->IsDetailTarget(node_id) &&
+ perf_manager->GetNextEntry(performance_entry_address, detail_type, entry_type, node_id)) {
+ command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start,
+ performance_entry_address);
+
+ initialized = true;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/detail_aspect.h b/src/audio_core/renderer/performance/detail_aspect.h
new file mode 100644
index 000000000..ee4ac2f76
--- /dev/null
+++ b/src/audio_core/renderer/performance/detail_aspect.h
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/performance/performance_entry_addresses.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class CommandGenerator;
+
+/**
+ * Holds detailed information about performance metrics, filled in by the AudioRenderer during
+ * Performance commands.
+ */
+class DetailAspect {
+public:
+ DetailAspect() = default;
+ DetailAspect(CommandGenerator& command_generator, PerformanceEntryType entry_type, s32 node_id,
+ PerformanceDetailType detail_type);
+
+ /// Command generator the command will be generated into
+ CommandGenerator& command_generator;
+ /// Addresses to be filled by the AudioRenderer
+ PerformanceEntryAddresses performance_entry_address{};
+ /// Is this detail aspect initialized?
+ bool initialized{};
+ /// Node id of this aspect
+ s32 node_id;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/entry_aspect.cpp b/src/audio_core/renderer/performance/entry_aspect.cpp
new file mode 100644
index 000000000..dd4165803
--- /dev/null
+++ b/src/audio_core/renderer/performance/entry_aspect.cpp
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/command/command_buffer.h"
+#include "audio_core/renderer/command/command_generator.h"
+#include "audio_core/renderer/performance/entry_aspect.h"
+
+namespace AudioCore::AudioRenderer {
+
+EntryAspect::EntryAspect(CommandGenerator& command_generator_, const PerformanceEntryType type,
+ const s32 node_id_)
+ : command_generator{command_generator_}, node_id{node_id_} {
+ auto perf_manager{command_generator.GetPerformanceManager()};
+ if (perf_manager != nullptr && perf_manager->IsInitialized() &&
+ perf_manager->GetNextEntry(performance_entry_address, type, node_id)) {
+ command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start,
+ performance_entry_address);
+
+ initialized = true;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/entry_aspect.h b/src/audio_core/renderer/performance/entry_aspect.h
new file mode 100644
index 000000000..01c1eb3f1
--- /dev/null
+++ b/src/audio_core/renderer/performance/entry_aspect.h
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/performance/performance_entry_addresses.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class CommandGenerator;
+
+/**
+ * Holds entry information about performance metrics, filled in by the AudioRenderer during
+ * Performance commands.
+ */
+class EntryAspect {
+public:
+ EntryAspect() = default;
+ EntryAspect(CommandGenerator& command_generator, PerformanceEntryType type, s32 node_id);
+
+ /// Command generator the command will be generated into
+ CommandGenerator& command_generator;
+ /// Addresses to be filled by the AudioRenderer
+ PerformanceEntryAddresses performance_entry_address{};
+ /// Is this detail aspect initialized?
+ bool initialized{};
+ /// Node id of this aspect
+ s32 node_id;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_detail.h b/src/audio_core/renderer/performance/performance_detail.h
new file mode 100644
index 000000000..3a4897e60
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_detail.h
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/performance/performance_entry.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+enum class PerformanceDetailType : u8 {
+ Invalid,
+ Unk1,
+ Unk2,
+ Unk3,
+ Unk4,
+ Unk5,
+ Unk6,
+ Unk7,
+ Unk8,
+ Unk9,
+ Unk10,
+ Unk11,
+ Unk12,
+ Unk13,
+};
+
+struct PerformanceDetailVersion1 {
+ /* 0x00 */ u32 node_id;
+ /* 0x04 */ u32 start_time;
+ /* 0x08 */ u32 processed_time;
+ /* 0x0C */ PerformanceDetailType detail_type;
+ /* 0x0D */ PerformanceEntryType entry_type;
+};
+static_assert(sizeof(PerformanceDetailVersion1) == 0x10,
+ "PerformanceDetailVersion1 has the worng size!");
+
+struct PerformanceDetailVersion2 {
+ /* 0x00 */ u32 node_id;
+ /* 0x04 */ u32 start_time;
+ /* 0x08 */ u32 processed_time;
+ /* 0x0C */ PerformanceDetailType detail_type;
+ /* 0x0D */ PerformanceEntryType entry_type;
+ /* 0x10 */ u32 unk_10;
+ /* 0x14 */ char unk14[0x4];
+};
+static_assert(sizeof(PerformanceDetailVersion2) == 0x18,
+ "PerformanceDetailVersion2 has the worng size!");
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_entry.h b/src/audio_core/renderer/performance/performance_entry.h
new file mode 100644
index 000000000..d1b21406b
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_entry.h
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+enum class PerformanceEntryType : u8 {
+ Invalid,
+ Voice,
+ SubMix,
+ FinalMix,
+ Sink,
+};
+
+struct PerformanceEntryVersion1 {
+ /* 0x00 */ u32 node_id;
+ /* 0x04 */ u32 start_time;
+ /* 0x08 */ u32 processed_time;
+ /* 0x0C */ PerformanceEntryType entry_type;
+};
+static_assert(sizeof(PerformanceEntryVersion1) == 0x10,
+ "PerformanceEntryVersion1 has the worng size!");
+
+struct PerformanceEntryVersion2 {
+ /* 0x00 */ u32 node_id;
+ /* 0x04 */ u32 start_time;
+ /* 0x08 */ u32 processed_time;
+ /* 0x0C */ PerformanceEntryType entry_type;
+ /* 0x0D */ char unk0D[0xB];
+};
+static_assert(sizeof(PerformanceEntryVersion2) == 0x18,
+ "PerformanceEntryVersion2 has the worng size!");
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_entry_addresses.h b/src/audio_core/renderer/performance/performance_entry_addresses.h
new file mode 100644
index 000000000..e381d765c
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_entry_addresses.h
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/common/common.h"
+
+namespace AudioCore::AudioRenderer {
+
+struct PerformanceEntryAddresses {
+ CpuAddr translated_address;
+ CpuAddr entry_start_time_offset;
+ CpuAddr header_entry_count_offset;
+ CpuAddr entry_processed_time_offset;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_frame_header.h b/src/audio_core/renderer/performance/performance_frame_header.h
new file mode 100644
index 000000000..707cc0afb
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_frame_header.h
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+struct PerformanceFrameHeaderVersion1 {
+ /* 0x00 */ u32 magic; // "PERF"
+ /* 0x04 */ u32 entry_count;
+ /* 0x08 */ u32 detail_count;
+ /* 0x0C */ u32 next_offset;
+ /* 0x10 */ u32 total_processing_time;
+ /* 0x14 */ u32 frame_index;
+};
+static_assert(sizeof(PerformanceFrameHeaderVersion1) == 0x18,
+ "PerformanceFrameHeaderVersion1 has the worng size!");
+
+struct PerformanceFrameHeaderVersion2 {
+ /* 0x00 */ u32 magic; // "PERF"
+ /* 0x04 */ u32 entry_count;
+ /* 0x08 */ u32 detail_count;
+ /* 0x0C */ u32 next_offset;
+ /* 0x10 */ u32 total_processing_time;
+ /* 0x14 */ u32 voices_dropped;
+ /* 0x18 */ u64 start_time;
+ /* 0x20 */ u32 frame_index;
+ /* 0x24 */ bool render_time_exceeded;
+ /* 0x25 */ char unk25[0xB];
+};
+static_assert(sizeof(PerformanceFrameHeaderVersion2) == 0x30,
+ "PerformanceFrameHeaderVersion2 has the worng size!");
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_manager.cpp b/src/audio_core/renderer/performance/performance_manager.cpp
new file mode 100644
index 000000000..fd5873e1e
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_manager.cpp
@@ -0,0 +1,645 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_funcs.h"
+
+namespace AudioCore::AudioRenderer {
+
+void PerformanceManager::CreateImpl(const size_t version) {
+ switch (version) {
+ case 1:
+ impl = std::make_unique<
+ PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1, PerformanceDetailVersion1>>();
+ break;
+ case 2:
+ impl = std::make_unique<
+ PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2, PerformanceDetailVersion2>>();
+ break;
+ default:
+ LOG_WARNING(Service_Audio, "Invalid PerformanceMetricsDataFormat {}, creating version 1",
+ static_cast<u32>(version));
+ impl = std::make_unique<
+ PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1, PerformanceDetailVersion1>>();
+ }
+}
+
+void PerformanceManager::Initialize(std::span<u8> workbuffer, const u64 workbuffer_size,
+ const AudioRendererParameterInternal& params,
+ const BehaviorInfo& behavior,
+ const MemoryPoolInfo& memory_pool) {
+ CreateImpl(behavior.GetPerformanceMetricsDataFormat());
+ impl->Initialize(workbuffer, workbuffer_size, params, behavior, memory_pool);
+}
+
+bool PerformanceManager::IsInitialized() const {
+ if (impl) {
+ return impl->IsInitialized();
+ }
+ return false;
+}
+
+u32 PerformanceManager::CopyHistories(u8* out_buffer, u64 out_size) {
+ if (impl) {
+ return impl->CopyHistories(out_buffer, out_size);
+ }
+ return 0;
+}
+
+bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
+ const PerformanceSysDetailType sys_detail_type,
+ const s32 node_id) {
+ if (impl) {
+ return impl->GetNextEntry(addresses, unk, sys_detail_type, node_id);
+ }
+ return false;
+}
+
+bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceEntryType entry_type, const s32 node_id) {
+ if (impl) {
+ return impl->GetNextEntry(addresses, entry_type, node_id);
+ }
+ return false;
+}
+
+bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceDetailType detail_type,
+ const PerformanceEntryType entry_type, const s32 node_id) {
+ if (impl) {
+ return impl->GetNextEntry(addresses, detail_type, entry_type, node_id);
+ }
+ return false;
+}
+
+void PerformanceManager::TapFrame(const bool dsp_behind, const u32 voices_dropped,
+ const u64 rendering_start_tick) {
+ if (impl) {
+ impl->TapFrame(dsp_behind, voices_dropped, rendering_start_tick);
+ }
+}
+
+bool PerformanceManager::IsDetailTarget(const u32 target_node_id) const {
+ if (impl) {
+ return impl->IsDetailTarget(target_node_id);
+ }
+ return false;
+}
+
+void PerformanceManager::SetDetailTarget(const u32 target_node_id) {
+ if (impl) {
+ impl->SetDetailTarget(target_node_id);
+ }
+}
+
+template <>
+void PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size,
+ const AudioRendererParameterInternal& params,
+ const BehaviorInfo& behavior,
+ const MemoryPoolInfo& memory_pool) {
+ workbuffer = workbuffer_;
+ entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1;
+ max_detail_count = MaxDetailEntries;
+ frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params);
+ const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)};
+ max_frames = frame_count - 1;
+ translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size);
+
+ // The first frame is the "current" frame we're writing to.
+ auto buffer_offset{workbuffer.data()};
+ frame_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset);
+ buffer_offset += sizeof(PerformanceFrameHeaderVersion1);
+ entry_buffer = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset), entries_per_frame};
+ buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1);
+ detail_buffer = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset), max_detail_count};
+
+ // After the current, is a ringbuffer of history frames, the current frame will be copied here
+ // before a new frame is written.
+ frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size);
+
+ // If there's room for any history frames.
+ if (frame_count >= 2) {
+ buffer_offset = frame_history.data();
+ frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset);
+ buffer_offset += sizeof(PerformanceFrameHeaderVersion1);
+ frame_history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset),
+ entries_per_frame};
+ buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1);
+ frame_history_details = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset),
+ max_detail_count};
+ } else {
+ frame_history_header = {};
+ frame_history_entries = {};
+ frame_history_details = {};
+ }
+
+ target_node_id = 0;
+ version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat());
+ entry_count = 0;
+ detail_count = 0;
+ frame_header->entry_count = 0;
+ frame_header->detail_count = 0;
+ output_frame_index = 0;
+ last_output_frame_index = 0;
+ is_initialized = true;
+}
+
+template <>
+bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1, PerformanceDetailVersion1>::IsInitialized()
+ const {
+ return is_initialized;
+}
+
+template <>
+u32 PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::CopyHistories(u8* out_buffer, u64 out_size) {
+ if (out_buffer == nullptr || out_size == 0 || !is_initialized) {
+ return 0;
+ }
+
+ // Are there any new frames waiting to be output?
+ if (last_output_frame_index == output_frame_index) {
+ return 0;
+ }
+
+ PerformanceFrameHeaderVersion1* out_header{nullptr};
+ u32 out_history_size{0};
+
+ while (last_output_frame_index != output_frame_index) {
+ PerformanceFrameHeaderVersion1* history_header{nullptr};
+ std::span<PerformanceEntryVersion1> history_entries{};
+ std::span<PerformanceDetailVersion1> history_details{};
+
+ if (max_frames > 0) {
+ auto frame_offset{&frame_history[last_output_frame_index * frame_size]};
+ history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(frame_offset);
+ frame_offset += sizeof(PerformanceFrameHeaderVersion1);
+ history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(frame_offset),
+ history_header->entry_count};
+ frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion1);
+ history_details = {reinterpret_cast<PerformanceDetailVersion1*>(frame_offset),
+ history_header->detail_count};
+ } else {
+ // Original code does not break here, but will crash when trying to dereference the
+ // header in the next if, so let's just skip this frame and continue...
+ // Hopefully this will not happen.
+ LOG_WARNING(Service_Audio,
+ "max_frames should not be 0! Skipping frame to avoid a crash");
+ last_output_frame_index++;
+ continue;
+ }
+
+ if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion1) +
+ history_header->detail_count * sizeof(PerformanceDetailVersion1) +
+ 2 * sizeof(PerformanceFrameHeaderVersion1)) {
+ break;
+ }
+
+ u32 out_offset{sizeof(PerformanceFrameHeaderVersion1)};
+ auto out_entries{std::span<PerformanceEntryVersion1>(
+ reinterpret_cast<PerformanceEntryVersion1*>(out_buffer + out_offset),
+ history_header->entry_count)};
+ u32 out_entry_count{0};
+ u32 total_processing_time{0};
+ for (auto& history_entry : history_entries) {
+ if (history_entry.processed_time > 0 || history_entry.start_time > 0) {
+ out_entries[out_entry_count++] = history_entry;
+ total_processing_time += history_entry.processed_time;
+ }
+ }
+
+ out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion1));
+ auto out_details{std::span<PerformanceDetailVersion1>(
+ reinterpret_cast<PerformanceDetailVersion1*>(out_buffer + out_offset),
+ history_header->detail_count)};
+ u32 out_detail_count{0};
+ for (auto& history_detail : history_details) {
+ if (history_detail.processed_time > 0 || history_detail.start_time > 0) {
+ out_details[out_detail_count++] = history_detail;
+ }
+ }
+
+ out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion1));
+ out_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(out_buffer);
+ out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F');
+ out_header->entry_count = out_entry_count;
+ out_header->detail_count = out_detail_count;
+ out_header->next_offset = out_offset;
+ out_header->total_processing_time = total_processing_time;
+ out_header->frame_index = history_header->frame_index;
+
+ out_history_size += out_offset;
+
+ out_buffer += out_offset;
+ out_size -= out_offset;
+ last_output_frame_index = (last_output_frame_index + 1) % max_frames;
+ }
+
+ // We're out of frames to output, so if there's enough left in the output buffer for another
+ // header, and we output at least 1 frame, set the next header to null.
+ if (out_size > sizeof(PerformanceFrameHeaderVersion1) && out_header != nullptr) {
+ std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion1));
+ }
+
+ return out_history_size;
+}
+
+template <>
+bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1, PerformanceDetailVersion1>::
+ GetNextEntry([[maybe_unused]] PerformanceEntryAddresses& addresses, [[maybe_unused]] u32** unk,
+ [[maybe_unused]] PerformanceSysDetailType sys_detail_type,
+ [[maybe_unused]] s32 node_id) {
+ return false;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceEntryType entry_type,
+ const s32 node_id) {
+ if (!is_initialized) {
+ return false;
+ }
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion1, entry_count);
+
+ auto entry{&entry_buffer[entry_count++]};
+ addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceEntryVersion1, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceEntryVersion1, processed_time);
+
+ std::memset(entry, 0, sizeof(PerformanceEntryVersion1));
+ entry->node_id = node_id;
+ entry->entry_type = entry_type;
+ return true;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceDetailType detail_type,
+ const PerformanceEntryType entry_type,
+ const s32 node_id) {
+ if (!is_initialized || detail_count > MaxDetailEntries) {
+ return false;
+ }
+
+ auto detail{&detail_buffer[detail_count++]};
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion1, detail_count);
+ addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion1, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion1, processed_time);
+
+ std::memset(detail, 0, sizeof(PerformanceDetailVersion1));
+ detail->node_id = node_id;
+ detail->entry_type = entry_type;
+ detail->detail_type = detail_type;
+ return true;
+}
+
+template <>
+void PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::TapFrame([[maybe_unused]] bool dsp_behind,
+ [[maybe_unused]] u32 voices_dropped,
+ [[maybe_unused]] u64 rendering_start_tick) {
+ if (!is_initialized) {
+ return;
+ }
+
+ if (max_frames > 0) {
+ if (!frame_history.empty() && !workbuffer.empty()) {
+ auto history_frame = reinterpret_cast<PerformanceFrameHeaderVersion1*>(
+ &frame_history[output_frame_index * frame_size]);
+ std::memcpy(history_frame, workbuffer.data(), frame_size);
+ history_frame->frame_index = history_frame_index++;
+ }
+ output_frame_index = (output_frame_index + 1) % max_frames;
+ }
+
+ entry_count = 0;
+ detail_count = 0;
+ frame_header->entry_count = 0;
+ frame_header->detail_count = 0;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::IsDetailTarget(const u32 target_node_id_) const {
+ return target_node_id == target_node_id_;
+}
+
+template <>
+void PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::SetDetailTarget(const u32 target_node_id_) {
+ target_node_id = target_node_id_;
+}
+
+template <>
+void PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size,
+ const AudioRendererParameterInternal& params,
+ const BehaviorInfo& behavior,
+ const MemoryPoolInfo& memory_pool) {
+ workbuffer = workbuffer_;
+ entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1;
+ max_detail_count = MaxDetailEntries;
+ frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params);
+ const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)};
+ max_frames = frame_count - 1;
+ translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size);
+
+ // The first frame is the "current" frame we're writing to.
+ auto buffer_offset{workbuffer.data()};
+ frame_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset);
+ buffer_offset += sizeof(PerformanceFrameHeaderVersion2);
+ entry_buffer = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset), entries_per_frame};
+ buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2);
+ detail_buffer = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset), max_detail_count};
+
+ // After the current, is a ringbuffer of history frames, the current frame will be copied here
+ // before a new frame is written.
+ frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size);
+
+ // If there's room for any history frames.
+ if (frame_count >= 2) {
+ buffer_offset = frame_history.data();
+ frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset);
+ buffer_offset += sizeof(PerformanceFrameHeaderVersion2);
+ frame_history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset),
+ entries_per_frame};
+ buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2);
+ frame_history_details = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset),
+ max_detail_count};
+ } else {
+ frame_history_header = {};
+ frame_history_entries = {};
+ frame_history_details = {};
+ }
+
+ target_node_id = 0;
+ version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat());
+ entry_count = 0;
+ detail_count = 0;
+ frame_header->entry_count = 0;
+ frame_header->detail_count = 0;
+ output_frame_index = 0;
+ last_output_frame_index = 0;
+ is_initialized = true;
+}
+
+template <>
+bool PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2, PerformanceDetailVersion2>::IsInitialized()
+ const {
+ return is_initialized;
+}
+
+template <>
+u32 PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::CopyHistories(u8* out_buffer, u64 out_size) {
+ if (out_buffer == nullptr || out_size == 0 || !is_initialized) {
+ return 0;
+ }
+
+ // Are there any new frames waiting to be output?
+ if (last_output_frame_index == output_frame_index) {
+ return 0;
+ }
+
+ PerformanceFrameHeaderVersion2* out_header{nullptr};
+ u32 out_history_size{0};
+
+ while (last_output_frame_index != output_frame_index) {
+ PerformanceFrameHeaderVersion2* history_header{nullptr};
+ std::span<PerformanceEntryVersion2> history_entries{};
+ std::span<PerformanceDetailVersion2> history_details{};
+
+ if (max_frames > 0) {
+ auto frame_offset{&frame_history[last_output_frame_index * frame_size]};
+ history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(frame_offset);
+ frame_offset += sizeof(PerformanceFrameHeaderVersion2);
+ history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(frame_offset),
+ history_header->entry_count};
+ frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion2);
+ history_details = {reinterpret_cast<PerformanceDetailVersion2*>(frame_offset),
+ history_header->detail_count};
+ } else {
+ // Original code does not break here, but will crash when trying to dereference the
+ // header in the next if, so let's just skip this frame and continue...
+ // Hopefully this will not happen.
+ LOG_WARNING(Service_Audio,
+ "max_frames should not be 0! Skipping frame to avoid a crash");
+ last_output_frame_index++;
+ continue;
+ }
+
+ if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion2) +
+ history_header->detail_count * sizeof(PerformanceDetailVersion2) +
+ 2 * sizeof(PerformanceFrameHeaderVersion2)) {
+ break;
+ }
+
+ u32 out_offset{sizeof(PerformanceFrameHeaderVersion2)};
+ auto out_entries{std::span<PerformanceEntryVersion2>(
+ reinterpret_cast<PerformanceEntryVersion2*>(out_buffer + out_offset),
+ history_header->entry_count)};
+ u32 out_entry_count{0};
+ u32 total_processing_time{0};
+ for (auto& history_entry : history_entries) {
+ if (history_entry.processed_time > 0 || history_entry.start_time > 0) {
+ out_entries[out_entry_count++] = history_entry;
+ total_processing_time += history_entry.processed_time;
+ }
+ }
+
+ out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion2));
+ auto out_details{std::span<PerformanceDetailVersion2>(
+ reinterpret_cast<PerformanceDetailVersion2*>(out_buffer + out_offset),
+ history_header->detail_count)};
+ u32 out_detail_count{0};
+ for (auto& history_detail : history_details) {
+ if (history_detail.processed_time > 0 || history_detail.start_time > 0) {
+ out_details[out_detail_count++] = history_detail;
+ }
+ }
+
+ out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion2));
+ out_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(out_buffer);
+ out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F');
+ out_header->entry_count = out_entry_count;
+ out_header->detail_count = out_detail_count;
+ out_header->next_offset = out_offset;
+ out_header->total_processing_time = total_processing_time;
+ out_header->voices_dropped = history_header->voices_dropped;
+ out_header->start_time = history_header->start_time;
+ out_header->frame_index = history_header->frame_index;
+ out_header->render_time_exceeded = history_header->render_time_exceeded;
+
+ out_history_size += out_offset;
+
+ out_buffer += out_offset;
+ out_size -= out_offset;
+ last_output_frame_index = (last_output_frame_index + 1) % max_frames;
+ }
+
+ // We're out of frames to output, so if there's enough left in the output buffer for another
+ // header, and we output at least 1 frame, set the next header to null.
+ if (out_size > sizeof(PerformanceFrameHeaderVersion2) && out_header != nullptr) {
+ std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion2));
+ }
+
+ return out_history_size;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
+ const PerformanceSysDetailType sys_detail_type,
+ const s32 node_id) {
+ if (!is_initialized || detail_count > MaxDetailEntries) {
+ return false;
+ }
+
+ auto detail{&detail_buffer[detail_count++]};
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion2, detail_count);
+ addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion2, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion2, processed_time);
+
+ std::memset(detail, 0, sizeof(PerformanceDetailVersion2));
+ detail->node_id = node_id;
+ detail->detail_type = static_cast<PerformanceDetailType>(sys_detail_type);
+
+ if (unk) {
+ *unk = &detail->unk_10;
+ }
+ return true;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceEntryType entry_type,
+ const s32 node_id) {
+ if (!is_initialized) {
+ return false;
+ }
+
+ auto entry{&entry_buffer[entry_count++]};
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion2, entry_count);
+ addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceEntryVersion2, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceEntryVersion2, processed_time);
+
+ std::memset(entry, 0, sizeof(PerformanceEntryVersion2));
+ entry->node_id = node_id;
+ entry->entry_type = entry_type;
+ return true;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceDetailType detail_type,
+ const PerformanceEntryType entry_type,
+ const s32 node_id) {
+ if (!is_initialized || detail_count > MaxDetailEntries) {
+ return false;
+ }
+
+ auto detail{&detail_buffer[detail_count++]};
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion2, detail_count);
+ addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion2, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion2, processed_time);
+
+ std::memset(detail, 0, sizeof(PerformanceDetailVersion2));
+ detail->node_id = node_id;
+ detail->entry_type = entry_type;
+ detail->detail_type = detail_type;
+ return true;
+}
+
+template <>
+void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::TapFrame(const bool dsp_behind,
+ const u32 voices_dropped,
+ const u64 rendering_start_tick) {
+ if (!is_initialized) {
+ return;
+ }
+
+ if (max_frames > 0) {
+ if (!frame_history.empty() && !workbuffer.empty()) {
+ auto history_frame{reinterpret_cast<PerformanceFrameHeaderVersion2*>(
+ &frame_history[output_frame_index * frame_size])};
+ std::memcpy(history_frame, workbuffer.data(), frame_size);
+ history_frame->render_time_exceeded = dsp_behind;
+ history_frame->voices_dropped = voices_dropped;
+ history_frame->start_time = rendering_start_tick;
+ history_frame->frame_index = history_frame_index++;
+ }
+ output_frame_index = (output_frame_index + 1) % max_frames;
+ }
+
+ entry_count = 0;
+ detail_count = 0;
+ frame_header->entry_count = 0;
+ frame_header->detail_count = 0;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::IsDetailTarget(const u32 target_node_id_) const {
+ return target_node_id == target_node_id_;
+}
+
+template <>
+void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::SetDetailTarget(const u32 target_node_id_) {
+ target_node_id = target_node_id_;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h
new file mode 100644
index 000000000..b65caa9b6
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_manager.h
@@ -0,0 +1,275 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+#include <span>
+
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/renderer/performance/performance_detail.h"
+#include "audio_core/renderer/performance/performance_entry.h"
+#include "audio_core/renderer/performance/performance_entry_addresses.h"
+#include "audio_core/renderer/performance/performance_frame_header.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class BehaviorInfo;
+class MemoryPoolInfo;
+
+enum class PerformanceVersion {
+ Version1,
+ Version2,
+};
+
+enum class PerformanceSysDetailType {
+ PcmInt16 = 15,
+ PcmFloat = 16,
+ Adpcm = 17,
+ LightLimiter = 37,
+};
+
+enum class PerformanceState {
+ Invalid,
+ Start,
+ Stop,
+};
+
+/**
+ * Manages performance information.
+ *
+ * The performance buffer is split into frames, each comprised of:
+ * Frame header - Information about the number of entries/details and some others
+ * Entries - Created when starting to generate types of commands, such as voice
+ * commands, mix commands, sink commands etc. Details - Created for specific commands
+ * within each group. Up to MaxDetailEntries per frame.
+ *
+ * A current frame is written to by the AudioRenderer, and before it processes the next command
+ * list, the current frame is copied to a ringbuffer of history frames. These frames are then
+ * output back to the game if it supplies a performance buffer to RequestUpdate.
+ *
+ * Two versions currently exist, version 2 adds a few extra fields to the header, and a new
+ * SysDetail type which is seemingly unused.
+ */
+class PerformanceManager {
+public:
+ static constexpr size_t MaxDetailEntries = 100;
+
+ struct InParameter {
+ /* 0x00 */ s32 target_node_id;
+ /* 0x04 */ char unk04[0xC];
+ };
+ static_assert(sizeof(InParameter) == 0x10,
+ "PerformanceManager::InParameter has the wrong size!");
+
+ struct OutStatus {
+ /* 0x00 */ s32 history_size;
+ /* 0x04 */ char unk04[0xC];
+ };
+ static_assert(sizeof(OutStatus) == 0x10, "PerformanceManager::OutStatus has the wrong size!");
+
+ /**
+ * Calculate the required size for the performance workbuffer.
+ *
+ * @param behavior - Check which version is supported.
+ * @param params - Input parameters.
+ *
+ * @return Required workbuffer size.
+ */
+ static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame(
+ const BehaviorInfo& behavior, const AudioRendererParameterInternal& params) {
+ u64 entry_count{params.voices + params.effects + params.sub_mixes + params.sinks + 1};
+ switch (behavior.GetPerformanceMetricsDataFormat()) {
+ case 1:
+ return sizeof(PerformanceFrameHeaderVersion1) +
+ PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) +
+ entry_count * sizeof(PerformanceEntryVersion1);
+ case 2:
+ return sizeof(PerformanceFrameHeaderVersion2) +
+ PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion2) +
+ entry_count * sizeof(PerformanceEntryVersion2);
+ }
+
+ LOG_WARNING(Service_Audio, "Invalid PerformanceMetrics version, assuming version 1");
+ return sizeof(PerformanceFrameHeaderVersion1) +
+ PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) +
+ entry_count * sizeof(PerformanceEntryVersion1);
+ }
+
+ virtual ~PerformanceManager() = default;
+
+ /**
+ * Initialize the performance manager.
+ *
+ * @param workbuffer - Workbuffer to use for performance frames.
+ * @param workbuffer_size - Size of the workbuffer.
+ * @param params - Input parameters.
+ * @param behavior - Behaviour to check version and data format.
+ * @param memory_pool - Used to translate the workbuffer address for the DSP.
+ */
+ virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
+ const AudioRendererParameterInternal& params,
+ const BehaviorInfo& behavior, const MemoryPoolInfo& memory_pool);
+
+ /**
+ * Check if the manager is initialized.
+ *
+ * @return True if initialized, otherwise false.
+ */
+ virtual bool IsInitialized() const;
+
+ /**
+ * Copy the waiting performance frames to the output buffer.
+ *
+ * @param out_buffer - Output buffer to store performance frames.
+ * @param out_size - Size of the output buffer.
+ * @return Size in bytes that were written to the buffer.
+ */
+ virtual u32 CopyHistories(u8* out_buffer, u64 out_size);
+
+ /**
+ * Setup a new sys detail in the current frame, filling in addresses with offsets to the
+ * current workbuffer, to be written by the AudioRenderer. Note: This version is
+ * unused/incomplete.
+ *
+ * @param addresses - Filled with pointers to the new entry, which should be passed to
+ * the AudioRenderer with Performance commands to be written.
+ * @param unk - Unknown.
+ * @param sys_detail_type - Sys detail type.
+ * @param node_id - Node id for this entry.
+ * @return True if a new entry was created and the offsets are valid, otherwise false.
+ */
+ virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
+ PerformanceSysDetailType sys_detail_type, s32 node_id);
+
+ /**
+ * Setup a new entry in the current frame, filling in addresses with offsets to the current
+ * workbuffer, to be written by the AudioRenderer.
+ *
+ * @param addresses - Filled with pointers to the new entry, which should be passed to
+ * the AudioRenderer with Performance commands to be written.
+ * @param entry_type - The type of this entry. See PerformanceEntryType
+ * @param node_id - Node id for this entry.
+ * @return True if a new entry was created and the offsets are valid, otherwise false.
+ */
+ virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type,
+ s32 node_id);
+
+ /**
+ * Setup a new detail in the current frame, filling in addresses with offsets to the current
+ * workbuffer, to be written by the AudioRenderer.
+ *
+ * @param addresses - Filled with pointers to the new detail, which should be passed
+ * to the AudioRenderer with Performance commands to be written.
+ * @param detail_type - Performance detail type.
+ * @param entry_type - The type of this detail. See PerformanceEntryType
+ * @param node_id - Node id for this detail.
+ * @return True if a new detail was created and the offsets are valid, otherwise false.
+ */
+ virtual bool GetNextEntry(PerformanceEntryAddresses& addresses,
+ PerformanceDetailType detail_type, PerformanceEntryType entry_type,
+ s32 node_id);
+
+ /**
+ * Save the current frame to the ring buffer.
+ *
+ * @param dsp_behind - Did the AudioRenderer fall behind and not
+ * finish processing the command list?
+ * @param voices_dropped - The number of voices that were dropped.
+ * @param rendering_start_tick - The tick rendering started.
+ */
+ virtual void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick);
+
+ /**
+ * Check if the node id is a detail type.
+ *
+ * @return True if the node is a detail type, otherwise false.
+ */
+ virtual bool IsDetailTarget(u32 target_node_id) const;
+
+ /**
+ * Set the given node to be a detail type.
+ *
+ * @param target_node_id - Node to set.
+ */
+ virtual void SetDetailTarget(u32 target_node_id);
+
+private:
+ /**
+ * Create the performance manager.
+ *
+ * @param version - Performance version to create.
+ */
+ void CreateImpl(size_t version);
+
+ std::unique_ptr<PerformanceManager>
+ /// Impl for the performance manager, may be version 1 or 2.
+ impl;
+};
+
+template <PerformanceVersion Version, typename FrameHeaderVersion, typename EntryVersion,
+ typename DetailVersion>
+class PerformanceManagerImpl : public PerformanceManager {
+public:
+ void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
+ const AudioRendererParameterInternal& params, const BehaviorInfo& behavior,
+ const MemoryPoolInfo& memory_pool) override;
+ bool IsInitialized() const override;
+ u32 CopyHistories(u8* out_buffer, u64 out_size) override;
+ bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
+ PerformanceSysDetailType sys_detail_type, s32 node_id) override;
+ bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type,
+ s32 node_id) override;
+ bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceDetailType detail_type,
+ PerformanceEntryType entry_type, s32 node_id) override;
+ void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick) override;
+ bool IsDetailTarget(u32 target_node_id) const override;
+ void SetDetailTarget(u32 target_node_id) override;
+
+private:
+ /// Workbuffer used to store the current performance frame
+ std::span<u8> workbuffer{};
+ /// DSP address of the workbuffer, used by the AudioRenderer
+ CpuAddr translated_buffer{};
+ /// Current frame index
+ u32 history_frame_index{};
+ /// Current frame header
+ FrameHeaderVersion* frame_header{};
+ /// Current frame entry buffer
+ std::span<EntryVersion> entry_buffer{};
+ /// Current frame detail buffer
+ std::span<DetailVersion> detail_buffer{};
+ /// Current frame entry count
+ u32 entry_count{};
+ /// Current frame detail count
+ u32 detail_count{};
+ /// Ringbuffer of previous frames
+ std::span<u8> frame_history{};
+ /// Current history frame header
+ FrameHeaderVersion* frame_history_header{};
+ /// Current history entry buffer
+ std::span<EntryVersion> frame_history_entries{};
+ /// Current history detail buffer
+ std::span<DetailVersion> frame_history_details{};
+ /// Current history ringbuffer write index
+ u32 output_frame_index{};
+ /// Last history frame index that was written back to the game
+ u32 last_output_frame_index{};
+ /// Maximum number of history frames in the ringbuffer
+ u32 max_frames{};
+ /// Number of entries per frame
+ u32 entries_per_frame{};
+ /// Maximum number of details per frame
+ u32 max_detail_count{};
+ /// Frame size in bytes
+ u64 frame_size{};
+ /// Is the performance manager initialized?
+ bool is_initialized{};
+ /// Target node id
+ u32 target_node_id{};
+ /// Performance version in use
+ PerformanceVersion version{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp
new file mode 100644
index 000000000..d91f10402
--- /dev/null
+++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp
@@ -0,0 +1,76 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/memory/pool_mapper.h"
+#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
+#include "audio_core/renderer/upsampler/upsampler_manager.h"
+
+namespace AudioCore::AudioRenderer {
+
+CircularBufferSinkInfo::CircularBufferSinkInfo() {
+ state.fill(0);
+ parameter.fill(0);
+ type = Type::CircularBufferSink;
+
+ auto state_{reinterpret_cast<CircularBufferState*>(state.data())};
+ state_->address_info.Setup(0, 0);
+}
+
+void CircularBufferSinkInfo::CleanUp() {
+ auto state_{reinterpret_cast<DeviceState*>(state.data())};
+
+ if (state_->upsampler_info) {
+ state_->upsampler_info->manager->Free(state_->upsampler_info);
+ state_->upsampler_info = nullptr;
+ }
+
+ parameter.fill(0);
+ type = Type::Invalid;
+}
+
+void CircularBufferSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
+ const InParameter& in_params, const PoolMapper& pool_mapper) {
+ const auto buffer_params{
+ reinterpret_cast<const CircularBufferInParameter*>(&in_params.circular_buffer)};
+ auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
+ auto current_state{reinterpret_cast<CircularBufferState*>(state.data())};
+
+ if (in_use == buffer_params->in_use && !buffer_unmapped) {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ out_status.writeOffset = current_state->last_pos2;
+ return;
+ }
+
+ node_id = in_params.node_id;
+ in_use = in_params.in_use;
+
+ if (in_use) {
+ buffer_unmapped =
+ !pool_mapper.TryAttachBuffer(error_info, current_state->address_info,
+ buffer_params->cpu_address, buffer_params->size);
+ *current_params = *buffer_params;
+ } else {
+ *current_params = *buffer_params;
+ }
+ out_status.writeOffset = current_state->last_pos2;
+}
+
+void CircularBufferSinkInfo::UpdateForCommandGeneration() {
+ if (in_use) {
+ auto params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
+ auto state_{reinterpret_cast<CircularBufferState*>(state.data())};
+
+ const auto pos{state_->current_pos};
+ state_->last_pos2 = state_->last_pos;
+ state_->last_pos = pos;
+
+ state_->current_pos += static_cast<s32>(params->input_count * params->sample_count *
+ GetSampleFormatByteSize(SampleFormat::PcmInt16));
+ if (params->size > 0) {
+ state_->current_pos %= params->size;
+ }
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.h b/src/audio_core/renderer/sink/circular_buffer_sink_info.h
new file mode 100644
index 000000000..3356213ea
--- /dev/null
+++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.h
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/sink/sink_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Info for a circular buffer sink.
+ */
+class CircularBufferSinkInfo : public SinkInfoBase {
+public:
+ CircularBufferSinkInfo();
+
+ /**
+ * Clean up for info, resetting it to a default state.
+ */
+ void CleanUp() override;
+
+ /**
+ * Update the info according to parameters, and write the current state to out_status.
+ *
+ * @param error_info - Output error code.
+ * @param out_status - Output status.
+ * @param in_params - Input parameters.
+ * @param pool_mapper - Used to map the circular buffer.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
+ const InParameter& in_params, const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the circular buffer on command generation, incrementing its current offsets.
+ */
+ void UpdateForCommandGeneration() override;
+};
+static_assert(sizeof(CircularBufferSinkInfo) <= sizeof(SinkInfoBase),
+ "CircularBufferSinkInfo is too large!");
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/device_sink_info.cpp b/src/audio_core/renderer/sink/device_sink_info.cpp
new file mode 100644
index 000000000..b7b3d6f1d
--- /dev/null
+++ b/src/audio_core/renderer/sink/device_sink_info.cpp
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/sink/device_sink_info.h"
+#include "audio_core/renderer/upsampler/upsampler_manager.h"
+
+namespace AudioCore::AudioRenderer {
+
+DeviceSinkInfo::DeviceSinkInfo() {
+ state.fill(0);
+ parameter.fill(0);
+ type = Type::DeviceSink;
+}
+
+void DeviceSinkInfo::CleanUp() {
+ auto state_{reinterpret_cast<DeviceState*>(state.data())};
+
+ if (state_->upsampler_info) {
+ state_->upsampler_info->manager->Free(state_->upsampler_info);
+ state_->upsampler_info = nullptr;
+ }
+
+ parameter.fill(0);
+ type = Type::Invalid;
+}
+
+void DeviceSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
+ const InParameter& in_params,
+ [[maybe_unused]] const PoolMapper& pool_mapper) {
+
+ const auto device_params{reinterpret_cast<const DeviceInParameter*>(&in_params.device)};
+ auto current_params{reinterpret_cast<DeviceInParameter*>(parameter.data())};
+
+ if (in_use == in_params.in_use) {
+ current_params->downmix_enabled = device_params->downmix_enabled;
+ current_params->downmix_coeff = device_params->downmix_coeff;
+ } else {
+ type = in_params.type;
+ in_use = in_params.in_use;
+ node_id = in_params.node_id;
+ *current_params = *device_params;
+ }
+
+ auto current_state{reinterpret_cast<DeviceState*>(state.data())};
+
+ for (size_t i = 0; i < current_state->downmix_coeff.size(); i++) {
+ current_state->downmix_coeff[i] = current_params->downmix_coeff[i];
+ }
+
+ std::memset(&out_status, 0, sizeof(OutStatus));
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void DeviceSinkInfo::UpdateForCommandGeneration() {}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/device_sink_info.h b/src/audio_core/renderer/sink/device_sink_info.h
new file mode 100644
index 000000000..a1c441454
--- /dev/null
+++ b/src/audio_core/renderer/sink/device_sink_info.h
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/sink/sink_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Info for a device sink.
+ */
+class DeviceSinkInfo : public SinkInfoBase {
+public:
+ DeviceSinkInfo();
+
+ /**
+ * Clean up for info, resetting it to a default state.
+ */
+ void CleanUp() override;
+
+ /**
+ * Update the info according to parameters, and write the current state to out_status.
+ *
+ * @param error_info - Output error code.
+ * @param out_status - Output status.
+ * @param in_params - Input parameters.
+ * @param pool_mapper - Unused.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
+ const InParameter& in_params, const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the device sink on command generation, unused.
+ */
+ void UpdateForCommandGeneration() override;
+};
+static_assert(sizeof(DeviceSinkInfo) <= sizeof(SinkInfoBase), "DeviceSinkInfo is too large!");
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/sink_context.cpp b/src/audio_core/renderer/sink/sink_context.cpp
new file mode 100644
index 000000000..634bc1cf9
--- /dev/null
+++ b/src/audio_core/renderer/sink/sink_context.cpp
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/sink/sink_context.h"
+
+namespace AudioCore::AudioRenderer {
+
+void SinkContext::Initialize(std::span<SinkInfoBase> sink_infos_, const u32 sink_count_) {
+ sink_infos = sink_infos_;
+ sink_count = sink_count_;
+}
+
+SinkInfoBase* SinkContext::GetInfo(const u32 index) {
+ return &sink_infos[index];
+}
+
+u32 SinkContext::GetCount() const {
+ return sink_count;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/sink_context.h b/src/audio_core/renderer/sink/sink_context.h
new file mode 100644
index 000000000..185572e29
--- /dev/null
+++ b/src/audio_core/renderer/sink/sink_context.h
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/sink/sink_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Manages output sinks.
+ */
+class SinkContext {
+public:
+ /**
+ * Initialize the sink context.
+ *
+ * @param sink_infos - Workbuffer for the sinks.
+ * @param sink_count - Number of sinks in the buffer.
+ */
+ void Initialize(std::span<SinkInfoBase> sink_infos, u32 sink_count);
+
+ /**
+ * Get a given index's info.
+ *
+ * @param index - Sink index to get.
+ * @return The sink info base for the given index.
+ */
+ SinkInfoBase* GetInfo(u32 index);
+
+ /**
+ * Get the current number of sinks.
+ *
+ * @return The number of sinks.
+ */
+ u32 GetCount() const;
+
+private:
+ /// Buffer of sink infos
+ std::span<SinkInfoBase> sink_infos{};
+ /// Number of sinks in the buffer
+ u32 sink_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/sink_info_base.cpp b/src/audio_core/renderer/sink/sink_info_base.cpp
new file mode 100644
index 000000000..4279beaa0
--- /dev/null
+++ b/src/audio_core/renderer/sink/sink_info_base.cpp
@@ -0,0 +1,51 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/memory/pool_mapper.h"
+#include "audio_core/renderer/sink/sink_info_base.h"
+
+namespace AudioCore::AudioRenderer {
+
+void SinkInfoBase::CleanUp() {
+ type = Type::Invalid;
+}
+
+void SinkInfoBase::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
+ [[maybe_unused]] const InParameter& in_params,
+ [[maybe_unused]] const PoolMapper& pool_mapper) {
+ std::memset(&out_status, 0, sizeof(OutStatus));
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void SinkInfoBase::UpdateForCommandGeneration() {}
+
+SinkInfoBase::DeviceState* SinkInfoBase::GetDeviceState() {
+ return reinterpret_cast<DeviceState*>(state.data());
+}
+
+SinkInfoBase::Type SinkInfoBase::GetType() const {
+ return type;
+}
+
+bool SinkInfoBase::IsUsed() const {
+ return in_use;
+}
+
+bool SinkInfoBase::ShouldSkip() const {
+ return buffer_unmapped;
+}
+
+u32 SinkInfoBase::GetNodeId() const {
+ return node_id;
+}
+
+u8* SinkInfoBase::GetState() {
+ return state.data();
+}
+
+u8* SinkInfoBase::GetParameter() {
+ return parameter.data();
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/sink_info_base.h b/src/audio_core/renderer/sink/sink_info_base.h
new file mode 100644
index 000000000..a1b855f20
--- /dev/null
+++ b/src/audio_core/renderer/sink/sink_info_base.h
@@ -0,0 +1,177 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/memory/address_info.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+struct UpsamplerInfo;
+class PoolMapper;
+
+/**
+ * Base for the circular buffer and device sinks, holding their states for the AudioRenderer and
+ * their parametetrs for generating sink commands.
+ */
+class SinkInfoBase {
+public:
+ enum class Type : u8 {
+ Invalid,
+ DeviceSink,
+ CircularBufferSink,
+ };
+
+ struct DeviceInParameter {
+ /* 0x000 */ char name[0x100];
+ /* 0x100 */ u32 input_count;
+ /* 0x104 */ std::array<s8, MaxChannels> inputs;
+ /* 0x10A */ char unk10A[0x1];
+ /* 0x10B */ bool downmix_enabled;
+ /* 0x10C */ std::array<f32, 4> downmix_coeff;
+ };
+ static_assert(sizeof(DeviceInParameter) == 0x11C, "DeviceInParameter has the wrong size!");
+
+ struct DeviceState {
+ /* 0x00 */ UpsamplerInfo* upsampler_info;
+ /* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff;
+ /* 0x18 */ char unk18[0x18];
+ };
+ static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!");
+
+ struct CircularBufferInParameter {
+ /* 0x00 */ u64 cpu_address;
+ /* 0x08 */ u32 size;
+ /* 0x0C */ u32 input_count;
+ /* 0x10 */ u32 sample_count;
+ /* 0x14 */ u32 previous_pos;
+ /* 0x18 */ SampleFormat format;
+ /* 0x1C */ std::array<s8, MaxChannels> inputs;
+ /* 0x22 */ bool in_use;
+ /* 0x23 */ char unk23[0x5];
+ };
+ static_assert(sizeof(CircularBufferInParameter) == 0x28,
+ "CircularBufferInParameter has the wrong size!");
+
+ struct CircularBufferState {
+ /* 0x00 */ u32 last_pos2;
+ /* 0x04 */ s32 current_pos;
+ /* 0x08 */ u32 last_pos;
+ /* 0x0C */ char unk0C[0x4];
+ /* 0x10 */ AddressInfo address_info;
+ };
+ static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!");
+
+ struct InParameter {
+ /* 0x000 */ Type type;
+ /* 0x001 */ bool in_use;
+ /* 0x004 */ u32 node_id;
+ /* 0x008 */ char unk08[0x18];
+ union {
+ /* 0x020 */ DeviceInParameter device;
+ /* 0x020 */ CircularBufferInParameter circular_buffer;
+ };
+ };
+ static_assert(sizeof(InParameter) == 0x140, "SinkInfoBase::InParameter has the wrong size!");
+
+ struct OutStatus {
+ /* 0x00 */ u32 writeOffset;
+ /* 0x04 */ char unk04[0x1C];
+ }; // size == 0x20
+ static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!");
+
+ virtual ~SinkInfoBase() = default;
+
+ /**
+ * Clean up for info, resetting it to a default state.
+ */
+ virtual void CleanUp();
+
+ /**
+ * Update the info according to parameters, and write the current state to out_status.
+ *
+ * @param error_info - Output error code.
+ * @param out_status - Output status.
+ * @param in_params - Input parameters.
+ * @param pool_mapper - Used to map the circular buffer.
+ */
+ virtual void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
+ [[maybe_unused]] const InParameter& in_params,
+ [[maybe_unused]] const PoolMapper& pool_mapper);
+
+ /**
+ * Update the circular buffer on command generation, incrementing its current offsets.
+ */
+ virtual void UpdateForCommandGeneration();
+
+ /**
+ * Get the state as a device sink.
+ *
+ * @return Device state.
+ */
+ DeviceState* GetDeviceState();
+
+ /**
+ * Get the type of this sink.
+ *
+ * @return Either Device, Circular, or Invalid.
+ */
+ Type GetType() const;
+
+ /**
+ * Check if this sink is in use.
+ *
+ * @return True if used, otherwise false.
+ */
+ bool IsUsed() const;
+
+ /**
+ * Check if this sink should be skipped for updates.
+ *
+ * @return True if it should be skipped, otherwise false.
+ */
+ bool ShouldSkip() const;
+
+ /**
+ * Get the node if of this sink.
+ *
+ * @return Node id for this sink.
+ */
+ u32 GetNodeId() const;
+
+ /**
+ * Get the state of this sink.
+ *
+ * @return Pointer to the state, must be cast to the correct type.
+ */
+ u8* GetState();
+
+ /**
+ * Get the parameters of this sink.
+ *
+ * @return Pointer to the parameters, must be cast to the correct type.
+ */
+ u8* GetParameter();
+
+protected:
+ /// Type of this sink
+ Type type{Type::Invalid};
+ /// Is this sink in use?
+ bool in_use{};
+ /// Is this sink's buffer unmapped? Circular only
+ bool buffer_unmapped{};
+ /// Node id for this sink
+ u32 node_id{};
+ /// State buffer for this sink
+ std::array<u8, std::max(sizeof(DeviceState), sizeof(CircularBufferState))> state{};
+ /// Parameter buffer for this sink
+ std::array<u8, std::max(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))>
+ parameter{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_context.cpp b/src/audio_core/renderer/splitter/splitter_context.cpp
new file mode 100644
index 000000000..7a23ba43f
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_context.cpp
@@ -0,0 +1,217 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/common/workbuffer_allocator.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/splitter/splitter_context.h"
+#include "common/alignment.h"
+
+namespace AudioCore::AudioRenderer {
+
+SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id,
+ const s32 destination_id) {
+ return splitter_infos[splitter_id].GetData(destination_id);
+}
+
+SplitterInfo& SplitterContext::GetInfo(const s32 splitter_id) {
+ return splitter_infos[splitter_id];
+}
+
+u32 SplitterContext::GetDataCount() const {
+ return destinations_count;
+}
+
+u32 SplitterContext::GetInfoCount() const {
+ return info_count;
+}
+
+SplitterDestinationData& SplitterContext::GetData(const u32 index) {
+ return splitter_destinations[index];
+}
+
+void SplitterContext::Setup(std::span<SplitterInfo> splitter_infos_, const u32 splitter_info_count_,
+ SplitterDestinationData* splitter_destinations_,
+ const u32 destination_count_, const bool splitter_bug_fixed_) {
+ splitter_infos = splitter_infos_;
+ info_count = splitter_info_count_;
+ splitter_destinations = splitter_destinations_;
+ destinations_count = destination_count_;
+ splitter_bug_fixed = splitter_bug_fixed_;
+}
+
+bool SplitterContext::UsingSplitter() const {
+ return splitter_infos.size() > 0 && info_count > 0 && splitter_destinations != nullptr &&
+ destinations_count > 0;
+}
+
+void SplitterContext::ClearAllNewConnectionFlag() {
+ for (s32 i = 0; i < info_count; i++) {
+ splitter_infos[i].SetNewConnectionFlag();
+ }
+}
+
+bool SplitterContext::Initialize(const BehaviorInfo& behavior,
+ const AudioRendererParameterInternal& params,
+ WorkbufferAllocator& allocator) {
+ if (behavior.IsSplitterSupported() && params.splitter_infos > 0 &&
+ params.splitter_destinations > 0) {
+ splitter_infos = allocator.Allocate<SplitterInfo>(params.splitter_infos, 0x10);
+
+ for (u32 i = 0; i < params.splitter_infos; i++) {
+ std::construct_at<SplitterInfo>(&splitter_infos[i], static_cast<s32>(i));
+ }
+
+ if (splitter_infos.size() == 0) {
+ splitter_infos = {};
+ return false;
+ }
+
+ splitter_destinations =
+ allocator.Allocate<SplitterDestinationData>(params.splitter_destinations, 0x10).data();
+
+ for (s32 i = 0; i < params.splitter_destinations; i++) {
+ std::construct_at<SplitterDestinationData>(&splitter_destinations[i], i);
+ }
+
+ if (params.splitter_destinations <= 0) {
+ splitter_infos = {};
+ splitter_destinations = nullptr;
+ return false;
+ }
+
+ Setup(splitter_infos, params.splitter_infos, splitter_destinations,
+ params.splitter_destinations, behavior.IsSplitterBugFixed());
+ }
+ return true;
+}
+
+bool SplitterContext::Update(const u8* input, u32& consumed_size) {
+ auto in_params{reinterpret_cast<const InParameterHeader*>(input)};
+
+ if (destinations_count == 0 || info_count == 0) {
+ consumed_size = 0;
+ return true;
+ }
+
+ if (in_params->magic != GetSplitterInParamHeaderMagic()) {
+ consumed_size = 0;
+ return false;
+ }
+
+ for (auto& splitter_info : splitter_infos) {
+ splitter_info.ClearNewConnectionFlag();
+ }
+
+ u32 offset{sizeof(InParameterHeader)};
+ offset = UpdateInfo(input, offset, in_params->info_count);
+ offset = UpdateData(input, offset, in_params->destination_count);
+
+ consumed_size = Common::AlignUp(offset, 0x10);
+ return true;
+}
+
+u32 SplitterContext::UpdateInfo(const u8* input, u32 offset, const u32 splitter_count) {
+ for (u32 i = 0; i < splitter_count; i++) {
+ auto info_header{reinterpret_cast<const SplitterInfo::InParameter*>(input + offset)};
+
+ if (info_header->magic != GetSplitterInfoMagic()) {
+ continue;
+ }
+
+ if (info_header->id < 0 || info_header->id > info_count) {
+ break;
+ }
+
+ auto& info{splitter_infos[info_header->id]};
+ RecomposeDestination(info, info_header);
+
+ offset += info.Update(info_header);
+ }
+
+ return offset;
+}
+
+u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) {
+ for (u32 i = 0; i < count; i++) {
+ auto data_header{
+ reinterpret_cast<const SplitterDestinationData::InParameter*>(input + offset)};
+
+ if (data_header->magic != GetSplitterSendDataMagic()) {
+ continue;
+ }
+
+ if (data_header->id < 0 || data_header->id > destinations_count) {
+ continue;
+ }
+
+ splitter_destinations[data_header->id].Update(*data_header);
+ offset += sizeof(SplitterDestinationData::InParameter);
+ }
+
+ return offset;
+}
+
+void SplitterContext::UpdateInternalState() {
+ for (s32 i = 0; i < info_count; i++) {
+ splitter_infos[i].UpdateInternalState();
+ }
+}
+
+void SplitterContext::RecomposeDestination(SplitterInfo& out_info,
+ const SplitterInfo::InParameter* info_header) {
+ auto destination{out_info.GetData(0)};
+ while (destination != nullptr) {
+ auto dest{destination->GetNext()};
+ destination->SetNext(nullptr);
+ destination = dest;
+ }
+ out_info.SetDestinations(nullptr);
+
+ auto dest_count{info_header->destination_count};
+ if (!splitter_bug_fixed) {
+ dest_count = std::min(dest_count, GetDestCountPerInfoForCompat());
+ }
+
+ if (dest_count == 0) {
+ return;
+ }
+
+ std::span<const u32> destination_ids{reinterpret_cast<const u32*>(&info_header[1]), dest_count};
+
+ auto head{&splitter_destinations[destination_ids[0]]};
+ auto current_destination{head};
+ for (u32 i = 1; i < dest_count; i++) {
+ auto next_destination{&splitter_destinations[destination_ids[i]]};
+ current_destination->SetNext(next_destination);
+ current_destination = next_destination;
+ }
+
+ out_info.SetDestinations(head);
+ out_info.SetDestinationCount(dest_count);
+}
+
+u32 SplitterContext::GetDestCountPerInfoForCompat() const {
+ if (info_count <= 0) {
+ return 0;
+ }
+ return static_cast<u32>(destinations_count / info_count);
+}
+
+u64 SplitterContext::CalcWorkBufferSize(const BehaviorInfo& behavior,
+ const AudioRendererParameterInternal& params) {
+ u64 size{0};
+ if (!behavior.IsSplitterSupported()) {
+ return size;
+ }
+
+ size += params.splitter_destinations * sizeof(SplitterDestinationData) +
+ params.splitter_infos * sizeof(SplitterInfo);
+
+ if (behavior.IsSplitterBugFixed()) {
+ size += Common::AlignUp(params.splitter_destinations * sizeof(u32), 0x10);
+ }
+ return size;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_context.h b/src/audio_core/renderer/splitter/splitter_context.h
new file mode 100644
index 000000000..cfd092b4f
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_context.h
@@ -0,0 +1,189 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/splitter/splitter_destinations_data.h"
+#include "audio_core/renderer/splitter/splitter_info.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+struct AudioRendererParameterInternal;
+class WorkbufferAllocator;
+
+namespace AudioRenderer {
+class BehaviorInfo;
+
+/**
+ * The splitter allows much more control over how sound is mixed together.
+ * Previously, one mix can only connect to one other, and you may need
+ * more mixes (and duplicate processing) to achieve the same result.
+ * With the splitter, many-to-one and one-to-many mixing is possible.
+ * This was added in revision 2.
+ * Had a bug with incorrect numbers of destinations, fixed in revision 5.
+ */
+class SplitterContext {
+ struct InParameterHeader {
+ /* 0x00 */ u32 magic; // 'SNDH'
+ /* 0x04 */ s32 info_count;
+ /* 0x08 */ s32 destination_count;
+ /* 0x0C */ char unk0C[0x14];
+ };
+ static_assert(sizeof(InParameterHeader) == 0x20,
+ "SplitterContext::InParameterHeader has the wrong size!");
+
+public:
+ /**
+ * Get a destination mix from the given splitter and destination index.
+ *
+ * @param splitter_id - Splitter index to get from.
+ * @param destination_id - Destination index within the splitter.
+ * @return Pointer to the found destination. May be nullptr.
+ */
+ SplitterDestinationData* GetDesintationData(s32 splitter_id, s32 destination_id);
+
+ /**
+ * Get a splitter from the given index.
+ *
+ * @param index - Index of the desired splitter.
+ * @return Splitter requested.
+ */
+ SplitterInfo& GetInfo(s32 index);
+
+ /**
+ * Get the total number of splitter destinations.
+ *
+ * @return Number of destiantions.
+ */
+ u32 GetDataCount() const;
+
+ /**
+ * Get the total number of splitters.
+ *
+ * @return Number of splitters.
+ */
+ u32 GetInfoCount() const;
+
+ /**
+ * Get a specific global destination.
+ *
+ * @param index - Index of the desired destination.
+ * @return The requested destination.
+ */
+ SplitterDestinationData& GetData(u32 index);
+
+ /**
+ * Check if the splitter is in use.
+ *
+ * @return True if any splitter or destination is in use, otherwise false.
+ */
+ bool UsingSplitter() const;
+
+ /**
+ * Mark all splitters as having new connections.
+ */
+ void ClearAllNewConnectionFlag();
+
+ /**
+ * Initialize the context.
+ *
+ * @param behavior - Used to check for splitter support.
+ * @param params - Input parameters.
+ * @param allocator - Allocator used to allocate workbuffer memory.
+ */
+ bool Initialize(const BehaviorInfo& behavior, const AudioRendererParameterInternal& params,
+ WorkbufferAllocator& allocator);
+
+ /**
+ * Update the context.
+ *
+ * @param input - Input buffer with the new info,
+ * expected to point to a InParameterHeader.
+ * @param consumed_size - Output with the number of bytes consumed from input.
+ */
+ bool Update(const u8* input, u32& consumed_size);
+
+ /**
+ * Update the splitters.
+ *
+ * @param input - Input buffer with the new info.
+ * @param offset - Current offset within the input buffer,
+ * input + offset should point to a SplitterInfo::InParameter.
+ * @param splitter_count - Number of splitters in the input buffer.
+ * @return Number of bytes consumed in input.
+ */
+ u32 UpdateInfo(const u8* input, u32 offset, u32 splitter_count);
+
+ /**
+ * Update the splitters.
+ *
+ * @param input - Input buffer with the new info.
+ * @param offset - Current offset within the input buffer,
+ * input + offset should point to a
+ * SplitterDestinationData::InParameter.
+ * @param destination_count - Number of destinations in the input buffer.
+ * @return Number of bytes consumed in input.
+ */
+ u32 UpdateData(const u8* input, u32 offset, u32 destination_count);
+
+ /**
+ * Update the state of all destinations in all splitters.
+ */
+ void UpdateInternalState();
+
+ /**
+ * Replace the given splitter's destinations with new ones.
+ *
+ * @param out_info - Splitter to recompose.
+ * @param info_header - Input parameters containing new destination ids.
+ */
+ void RecomposeDestination(SplitterInfo& out_info, const SplitterInfo::InParameter* info_header);
+
+ /**
+ * Old calculation for destinations, this is the thing the splitter bug fixes.
+ * Left for compatibility, and now min'd with the actual count to not bug.
+ *
+ * @return Number of splitter destinations.
+ */
+ u32 GetDestCountPerInfoForCompat() const;
+
+ /**
+ * Calculate the size of the required workbuffer for splitters and destinations.
+ *
+ * @param behavior - Used to check splitter features.
+ * @param params - Input parameters with splitter/destination counts.
+ * @return Required buffer size.
+ */
+ static u64 CalcWorkBufferSize(const BehaviorInfo& behavior,
+ const AudioRendererParameterInternal& params);
+
+private:
+ /**
+ * Setup the context.
+ *
+ * @param splitter_infos - Workbuffer for splitters.
+ * @param splitter_info_count - Number of splitters in the workbuffer.
+ * @param splitter_destinations - Workbuffer for splitter destinations.
+ * @param destination_count - Number of destinations in the workbuffer.
+ * @param splitter_bug_fixed - Is the splitter bug fixed?
+ */
+ void Setup(std::span<SplitterInfo> splitter_infos, u32 splitter_info_count,
+ SplitterDestinationData* splitter_destinations, u32 destination_count,
+ bool splitter_bug_fixed);
+
+ /// Workbuffer for splitters
+ std::span<SplitterInfo> splitter_infos{};
+ /// Number of splitters in buffer
+ s32 info_count{};
+ /// Workbuffer for destinations
+ SplitterDestinationData* splitter_destinations{};
+ /// Number of destinations in buffer
+ s32 destinations_count{};
+ /// Is the splitter bug fixed?
+ bool splitter_bug_fixed{};
+};
+
+} // namespace AudioRenderer
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp
new file mode 100644
index 000000000..b27d44896
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp
@@ -0,0 +1,87 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/splitter/splitter_destinations_data.h"
+
+namespace AudioCore::AudioRenderer {
+
+SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {}
+
+void SplitterDestinationData::ClearMixVolume() {
+ mix_volumes.fill(0.0f);
+ prev_mix_volumes.fill(0.0f);
+}
+
+s32 SplitterDestinationData::GetId() const {
+ return id;
+}
+
+bool SplitterDestinationData::IsConfigured() const {
+ return in_use && destination_id != UnusedMixId;
+}
+
+s32 SplitterDestinationData::GetMixId() const {
+ return destination_id;
+}
+
+f32 SplitterDestinationData::GetMixVolume(const u32 index) const {
+ if (index >= mix_volumes.size()) {
+ LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolume Invalid index {}", index);
+ return 0.0f;
+ }
+ return mix_volumes[index];
+}
+
+std::span<f32> SplitterDestinationData::GetMixVolume() {
+ return mix_volumes;
+}
+
+f32 SplitterDestinationData::GetMixVolumePrev(const u32 index) const {
+ if (index >= prev_mix_volumes.size()) {
+ LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolumePrev Invalid index {}",
+ index);
+ return 0.0f;
+ }
+ return prev_mix_volumes[index];
+}
+
+std::span<f32> SplitterDestinationData::GetMixVolumePrev() {
+ return prev_mix_volumes;
+}
+
+void SplitterDestinationData::Update(const InParameter& params) {
+ if (params.id != id || params.magic != GetSplitterSendDataMagic()) {
+ return;
+ }
+
+ destination_id = params.mix_id;
+ mix_volumes = params.mix_volumes;
+
+ if (!in_use && params.in_use) {
+ prev_mix_volumes = mix_volumes;
+ need_update = false;
+ }
+
+ in_use = params.in_use;
+}
+
+void SplitterDestinationData::MarkAsNeedToUpdateInternalState() {
+ need_update = true;
+}
+
+void SplitterDestinationData::UpdateInternalState() {
+ if (in_use && need_update) {
+ prev_mix_volumes = mix_volumes;
+ }
+ need_update = false;
+}
+
+SplitterDestinationData* SplitterDestinationData::GetNext() const {
+ return next;
+}
+
+void SplitterDestinationData::SetNext(SplitterDestinationData* next_) {
+ next = next_;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.h b/src/audio_core/renderer/splitter/splitter_destinations_data.h
new file mode 100644
index 000000000..bd3d55748
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_destinations_data.h
@@ -0,0 +1,135 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Represents a mixing node, can be connected to a previous and next destination forming a chain
+ * that a certain mix buffer will pass through to output.
+ */
+class SplitterDestinationData {
+public:
+ struct InParameter {
+ /* 0x00 */ u32 magic; // 'SNDD'
+ /* 0x04 */ s32 id;
+ /* 0x08 */ std::array<f32, MaxMixBuffers> mix_volumes;
+ /* 0x68 */ u32 mix_id;
+ /* 0x6C */ bool in_use;
+ };
+ static_assert(sizeof(InParameter) == 0x70,
+ "SplitterDestinationData::InParameter has the wrong size!");
+
+ SplitterDestinationData(s32 id);
+
+ /**
+ * Reset the mix volumes for this destination.
+ */
+ void ClearMixVolume();
+
+ /**
+ * Get the id of this destination.
+ *
+ * @return Id for this destination.
+ */
+ s32 GetId() const;
+
+ /**
+ * Check if this destination is correctly configured.
+ *
+ * @return True if configured, otherwise false.
+ */
+ bool IsConfigured() const;
+
+ /**
+ * Get the mix id for this destination.
+ *
+ * @return Mix id for this destination.
+ */
+ s32 GetMixId() const;
+
+ /**
+ * Get the current mix volume of a given index in this destination.
+ *
+ * @param index - Mix buffer index to get the volume for.
+ * @return Current volume of the specified mix.
+ */
+ f32 GetMixVolume(u32 index) const;
+
+ /**
+ * Get the current mix volumes for all mix buffers in this destination.
+ *
+ * @return Span of current mix buffer volumes.
+ */
+ std::span<f32> GetMixVolume();
+
+ /**
+ * Get the previous mix volume of a given index in this destination.
+ *
+ * @param index - Mix buffer index to get the volume for.
+ * @return Previous volume of the specified mix.
+ */
+ f32 GetMixVolumePrev(u32 index) const;
+
+ /**
+ * Get the previous mix volumes for all mix buffers in this destination.
+ *
+ * @return Span of previous mix buffer volumes.
+ */
+ std::span<f32> GetMixVolumePrev();
+
+ /**
+ * Update this destination.
+ *
+ * @param params - Inpout parameters to update the destination.
+ */
+ void Update(const InParameter& params);
+
+ /**
+ * Mark this destination as needing its volumes updated.
+ */
+ void MarkAsNeedToUpdateInternalState();
+
+ /**
+ * Copy current volumes to previous if an update is required.
+ */
+ void UpdateInternalState();
+
+ /**
+ * Get the next destination in the mix chain.
+ *
+ * @return The next splitter destination, may be nullptr if this is the last in the chain.
+ */
+ SplitterDestinationData* GetNext() const;
+
+ /**
+ * Set the next destination in the mix chain.
+ *
+ * @param next - Destination this one is to be connected to.
+ */
+ void SetNext(SplitterDestinationData* next);
+
+private:
+ /// Id of this destination
+ const s32 id;
+ /// Mix id this destination represents
+ s32 destination_id{UnusedMixId};
+ /// Current mix volumes
+ std::array<f32, MaxMixBuffers> mix_volumes{0.0f};
+ /// Previous mix volumes
+ std::array<f32, MaxMixBuffers> prev_mix_volumes{0.0f};
+ /// Next destination in the mix chain
+ SplitterDestinationData* next{};
+ /// Is this destiantion in use?
+ bool in_use{};
+ /// Does this destiantion need its volumes updated?
+ bool need_update{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_info.cpp b/src/audio_core/renderer/splitter/splitter_info.cpp
new file mode 100644
index 000000000..1aee6720b
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_info.cpp
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/splitter/splitter_info.h"
+
+namespace AudioCore::AudioRenderer {
+
+SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {}
+
+void SplitterInfo::InitializeInfos(SplitterInfo* splitters, const u32 count) {
+ if (splitters == nullptr) {
+ return;
+ }
+
+ for (u32 i = 0; i < count; i++) {
+ auto& splitter{splitters[i]};
+ splitter.destinations = nullptr;
+ splitter.destination_count = 0;
+ splitter.has_new_connection = true;
+ }
+}
+
+u32 SplitterInfo::Update(const InParameter* params) {
+ if (params->id != id) {
+ return 0;
+ }
+ sample_rate = params->sample_rate;
+ has_new_connection = true;
+ return static_cast<u32>((sizeof(InParameter) + 3 * sizeof(s32)) +
+ params->destination_count * sizeof(s32));
+}
+
+SplitterDestinationData* SplitterInfo::GetData(const u32 destination_id) {
+ auto out_destination{destinations};
+ u32 i{0};
+ while (i < destination_id) {
+ if (out_destination == nullptr) {
+ break;
+ }
+ out_destination = out_destination->GetNext();
+ i++;
+ }
+
+ return out_destination;
+}
+
+u32 SplitterInfo::GetDestinationCount() const {
+ return destination_count;
+}
+
+void SplitterInfo::SetDestinationCount(const u32 count) {
+ destination_count = count;
+}
+
+bool SplitterInfo::HasNewConnection() const {
+ return has_new_connection;
+}
+
+void SplitterInfo::ClearNewConnectionFlag() {
+ has_new_connection = false;
+}
+
+void SplitterInfo::SetNewConnectionFlag() {
+ has_new_connection = true;
+}
+
+void SplitterInfo::UpdateInternalState() {
+ auto destination{destinations};
+ while (destination != nullptr) {
+ destination->UpdateInternalState();
+ destination = destination->GetNext();
+ }
+}
+
+void SplitterInfo::SetDestinations(SplitterDestinationData* destinations_) {
+ destinations = destinations_;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_info.h b/src/audio_core/renderer/splitter/splitter_info.h
new file mode 100644
index 000000000..d1d75064c
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_info.h
@@ -0,0 +1,107 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/splitter/splitter_destinations_data.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Represents a splitter, wraps multiple output destinations to split an input mix into.
+ */
+class SplitterInfo {
+public:
+ struct InParameter {
+ /* 0x00 */ u32 magic; // 'SNDI'
+ /* 0x04 */ s32 id;
+ /* 0x08 */ u32 sample_rate;
+ /* 0x0C */ u32 destination_count;
+ };
+ static_assert(sizeof(InParameter) == 0x10, "SplitterInfo::InParameter has the wrong size!");
+
+ explicit SplitterInfo(s32 id);
+
+ /**
+ * Initialize the given splitters.
+ *
+ * @param splitters - Splitters to initialize.
+ * @param count - Number of splitters given.
+ */
+ static void InitializeInfos(SplitterInfo* splitters, u32 count);
+
+ /**
+ * Update this splitter.
+ *
+ * @param params - Input parameters to update with.
+ * @return The size in bytes of this splitter.
+ */
+ u32 Update(const InParameter* params);
+
+ /**
+ * Get a destination in this splitter.
+ *
+ * @param id - Destination id to get.
+ * @return Pointer to the destination, may be nullptr.
+ */
+ SplitterDestinationData* GetData(u32 id);
+
+ /**
+ * Get the number of destinations in this splitter.
+ *
+ * @return The number of destiantions.
+ */
+ u32 GetDestinationCount() const;
+
+ /**
+ * Set the number of destinations in this splitter.
+ *
+ * @param count - The new number of destiantions.
+ */
+ void SetDestinationCount(u32 count);
+
+ /**
+ * Check if the splitter has a new connection.
+ *
+ * @return True if there is a new connection, otherwise false.
+ */
+ bool HasNewConnection() const;
+
+ /**
+ * Reset the new connection flag.
+ */
+ void ClearNewConnectionFlag();
+
+ /**
+ * Mark as having a new connection.
+ */
+ void SetNewConnectionFlag();
+
+ /**
+ * Update the state of all destinations.
+ */
+ void UpdateInternalState();
+
+ /**
+ * Set this splitter's destinations.
+ *
+ * @param destinations - The new destination list for this splitter.
+ */
+ void SetDestinations(SplitterDestinationData* destinations);
+
+private:
+ /// Id of this splitter
+ s32 id;
+ /// Sample rate of this splitter
+ u32 sample_rate{};
+ /// Number of destinations in this splitter
+ u32 destination_count{};
+ /// Does this splitter have a new connection?
+ bool has_new_connection{true};
+ /// Pointer to the destinations of this splitter
+ SplitterDestinationData* destinations{};
+ /// Number of channels this splitter manages
+ u32 channel_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp
new file mode 100644
index 000000000..7a217969e
--- /dev/null
+++ b/src/audio_core/renderer/system.cpp
@@ -0,0 +1,802 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <chrono>
+#include <span>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/common/common.h"
+#include "audio_core/common/feature_support.h"
+#include "audio_core/common/workbuffer_allocator.h"
+#include "audio_core/renderer/adsp/adsp.h"
+#include "audio_core/renderer/behavior/info_updater.h"
+#include "audio_core/renderer/command/command_buffer.h"
+#include "audio_core/renderer/command/command_generator.h"
+#include "audio_core/renderer/command/command_list_header.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "audio_core/renderer/effect/effect_result_state.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "audio_core/renderer/memory/pool_mapper.h"
+#include "audio_core/renderer/mix/mix_info.h"
+#include "audio_core/renderer/nodes/edge_matrix.h"
+#include "audio_core/renderer/nodes/node_states.h"
+#include "audio_core/renderer/sink/sink_info_base.h"
+#include "audio_core/renderer/system.h"
+#include "audio_core/renderer/upsampler/upsampler_info.h"
+#include "audio_core/renderer/voice/voice_channel_resource.h"
+#include "audio_core/renderer/voice/voice_info.h"
+#include "audio_core/renderer/voice/voice_state.h"
+#include "common/alignment.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_transfer_memory.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer {
+
+u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) {
+ BehaviorInfo behavior;
+ behavior.SetUserLibRevision(params.revision);
+
+ u64 size{0};
+
+ size += Common::AlignUp(params.mixes * sizeof(s32), 0x40);
+ size += params.sub_mixes * MaxEffects * sizeof(s32);
+ size += (params.sub_mixes + 1) * sizeof(MixInfo);
+ size += params.voices * (sizeof(VoiceInfo) + sizeof(VoiceChannelResource) + sizeof(VoiceState));
+ size += Common::AlignUp((params.sub_mixes + 1) * sizeof(MixInfo*), 0x10);
+ size += Common::AlignUp(params.voices * sizeof(VoiceInfo*), 0x10);
+ size += Common::AlignUp(((params.sinks + params.sub_mixes) * TargetSampleCount * sizeof(s32) +
+ params.sample_count * sizeof(s32)) *
+ (params.mixes + MaxChannels),
+ 0x40);
+
+ if (behavior.IsSplitterSupported()) {
+ const auto node_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)};
+ const auto edge_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)};
+ size += Common::AlignUp(node_size + edge_size, 0x10);
+ }
+
+ size += SplitterContext::CalcWorkBufferSize(behavior, params);
+ size += (params.effects + params.voices * MaxWaveBuffers) * sizeof(MemoryPoolInfo);
+
+ if (behavior.IsEffectInfoVersion2Supported()) {
+ size += params.effects * sizeof(EffectResultState);
+ }
+ size += 0x50;
+
+ size = Common::AlignUp(size, 0x40);
+
+ size += (params.sinks + params.sub_mixes) * sizeof(UpsamplerInfo);
+ size += params.effects * sizeof(EffectInfoBase);
+ size += Common::AlignUp(params.voices * sizeof(VoiceState), 0x40);
+ size += params.sinks * sizeof(SinkInfoBase);
+
+ if (behavior.IsEffectInfoVersion2Supported()) {
+ size += params.effects * sizeof(EffectResultState);
+ }
+
+ if (params.perf_frames > 0) {
+ auto perf_size{PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame(
+ behavior, params)};
+ size += Common::AlignUp(perf_size * (params.perf_frames + 1) + 0xC0, 0x100);
+ }
+
+ if (behavior.IsVariadicCommandBufferSizeSupported()) {
+ size += CommandGenerator::CalculateCommandBufferSize(behavior, params) + (0x40 - 1) * 2;
+ } else {
+ size += 0x18000 + (0x40 - 1) * 2;
+ }
+
+ size = Common::AlignUp(size, 0x1000);
+ return size;
+}
+
+System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_)
+ : core{core_}, adsp{core.AudioCore().GetADSP()}, adsp_rendered_event{adsp_rendered_event_} {}
+
+Result System::Initialize(const AudioRendererParameterInternal& params,
+ Kernel::KTransferMemory* transfer_memory, const u64 transfer_memory_size,
+ const u32 process_handle_, const u64 applet_resource_user_id_,
+ const s32 session_id_) {
+ if (!CheckValidRevision(params.revision)) {
+ return Service::Audio::ERR_INVALID_REVISION;
+ }
+
+ if (GetWorkBufferSize(params) > transfer_memory_size) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ if (process_handle_ == 0) {
+ return Service::Audio::ERR_INVALID_PROCESS_HANDLE;
+ }
+
+ behavior.SetUserLibRevision(params.revision);
+
+ process_handle = process_handle_;
+ applet_resource_user_id = applet_resource_user_id_;
+ session_id = session_id_;
+
+ sample_rate = params.sample_rate;
+ sample_count = params.sample_count;
+ mix_buffer_count = static_cast<s16>(params.mixes);
+ voice_channels = MaxChannels;
+ upsampler_count = params.sinks + params.sub_mixes;
+ memory_pool_count = params.effects + params.voices * MaxWaveBuffers;
+ render_device = params.rendering_device;
+ execution_mode = params.execution_mode;
+
+ core.Memory().ZeroBlock(*core.Kernel().CurrentProcess(), transfer_memory->GetSourceAddress(),
+ transfer_memory_size);
+
+ // Note: We're not actually using the transfer memory because it's a pain to code for.
+ // Allocate the memory normally instead and hope the game doesn't try to read anything back
+ workbuffer = std::make_unique<u8[]>(transfer_memory_size);
+ workbuffer_size = transfer_memory_size;
+
+ PoolMapper pool_mapper(process_handle, false);
+ pool_mapper.InitializeSystemPool(memory_pool_info, workbuffer.get(), workbuffer_size);
+
+ WorkbufferAllocator allocator({workbuffer.get(), workbuffer_size}, workbuffer_size);
+
+ samples_workbuffer =
+ allocator.Allocate<s32>((voice_channels + mix_buffer_count) * sample_count, 0x10);
+ if (samples_workbuffer.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ auto upsampler_workbuffer{allocator.Allocate<s32>(
+ (voice_channels + mix_buffer_count) * TargetSampleCount * upsampler_count, 0x10)};
+ if (upsampler_workbuffer.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ depop_buffer =
+ allocator.Allocate<s32>(Common::AlignUp(static_cast<u32>(mix_buffer_count), 0x40), 0x40);
+ if (depop_buffer.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ // invalidate samples_workbuffer DSP cache
+
+ auto voice_infos{allocator.Allocate<VoiceInfo>(params.voices, 0x10)};
+ for (auto& voice_info : voice_infos) {
+ std::construct_at<VoiceInfo>(&voice_info);
+ }
+
+ if (voice_infos.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ auto sorted_voice_infos{allocator.Allocate<VoiceInfo*>(params.voices, 0x10)};
+ if (sorted_voice_infos.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ std::memset(sorted_voice_infos.data(), 0, sorted_voice_infos.size_bytes());
+
+ auto voice_channel_resources{allocator.Allocate<VoiceChannelResource>(params.voices, 0x10)};
+ u32 i{0};
+ for (auto& voice_channel_resource : voice_channel_resources) {
+ std::construct_at<VoiceChannelResource>(&voice_channel_resource, i++);
+ }
+
+ if (voice_channel_resources.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ auto voice_cpu_states{allocator.Allocate<VoiceState>(params.voices, 0x10)};
+ if (voice_cpu_states.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ for (auto& voice_state : voice_cpu_states) {
+ voice_state = {};
+ }
+
+ auto mix_infos{allocator.Allocate<MixInfo>(params.sub_mixes + 1, 0x10)};
+
+ if (mix_infos.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ u32 effect_process_order_count{0};
+ std::span<s32> effect_process_order_buffer{};
+
+ if (params.effects > 0) {
+ effect_process_order_count = params.effects * (params.sub_mixes + 1);
+ effect_process_order_buffer = allocator.Allocate<s32>(effect_process_order_count, 0x10);
+ if (effect_process_order_buffer.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+ }
+
+ i = 0;
+ for (auto& mix_info : mix_infos) {
+ std::construct_at<MixInfo>(
+ &mix_info, effect_process_order_buffer.subspan(i * params.effects, params.effects),
+ params.effects, this->behavior);
+ i++;
+ }
+
+ auto sorted_mix_infos{allocator.Allocate<MixInfo*>(params.sub_mixes + 1, 0x10)};
+ if (sorted_mix_infos.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ std::memset(sorted_mix_infos.data(), 0, sorted_mix_infos.size_bytes());
+
+ if (behavior.IsSplitterSupported()) {
+ u64 node_state_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)};
+ u64 edge_matrix_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)};
+
+ auto node_states_workbuffer{allocator.Allocate<u8>(node_state_size, 1)};
+ auto edge_matrix_workbuffer{allocator.Allocate<u8>(edge_matrix_size, 1)};
+
+ if (node_states_workbuffer.empty() || edge_matrix_workbuffer.size() == 0) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1,
+ effect_process_order_buffer, effect_process_order_count,
+ node_states_workbuffer, node_state_size, edge_matrix_workbuffer,
+ edge_matrix_size);
+ } else {
+ mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1,
+ effect_process_order_buffer, effect_process_order_count, {}, 0, {},
+ 0);
+ }
+
+ upsampler_manager = allocator.Allocate<UpsamplerManager>(1, 0x10).data();
+ if (upsampler_manager == nullptr) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ memory_pool_workbuffer = allocator.Allocate<MemoryPoolInfo>(memory_pool_count, 0x10);
+ for (auto& memory_pool : memory_pool_workbuffer) {
+ std::construct_at<MemoryPoolInfo>(&memory_pool, MemoryPoolInfo::Location::DSP);
+ }
+
+ if (memory_pool_workbuffer.empty() && memory_pool_count > 0) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ if (!splitter_context.Initialize(behavior, params, allocator)) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ std::span<EffectResultState> effect_result_states_cpu{};
+ if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) {
+ effect_result_states_cpu = allocator.Allocate<EffectResultState>(params.effects, 0x10);
+ if (effect_result_states_cpu.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+ std::memset(effect_result_states_cpu.data(), 0, effect_result_states_cpu.size_bytes());
+ }
+
+ allocator.Align(0x40);
+
+ unk_2B0 = allocator.GetSize() - allocator.GetCurrentOffset();
+ unk_2A8 = {&workbuffer[allocator.GetCurrentOffset()], unk_2B0};
+
+ upsampler_infos = allocator.Allocate<UpsamplerInfo>(upsampler_count, 0x40);
+ for (auto& upsampler_info : upsampler_infos) {
+ std::construct_at<UpsamplerInfo>(&upsampler_info);
+ }
+
+ std::construct_at<UpsamplerManager>(upsampler_manager, upsampler_count, upsampler_infos,
+ upsampler_workbuffer);
+
+ if (upsampler_infos.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ auto effect_infos{allocator.Allocate<EffectInfoBase>(params.effects, 0x40)};
+ for (auto& effect_info : effect_infos) {
+ std::construct_at<EffectInfoBase>(&effect_info);
+ }
+
+ if (effect_infos.empty() && params.effects > 0) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ std::span<EffectResultState> effect_result_states_dsp{};
+ if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) {
+ effect_result_states_dsp = allocator.Allocate<EffectResultState>(params.effects, 0x40);
+ if (effect_result_states_dsp.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+ std::memset(effect_result_states_dsp.data(), 0, effect_result_states_dsp.size_bytes());
+ }
+
+ effect_context.Initialize(effect_infos, params.effects, effect_result_states_cpu,
+ effect_result_states_dsp, effect_result_states_dsp.size());
+
+ auto sinks{allocator.Allocate<SinkInfoBase>(params.sinks, 0x10)};
+ for (auto& sink : sinks) {
+ std::construct_at<SinkInfoBase>(&sink);
+ }
+
+ if (sinks.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ sink_context.Initialize(sinks, params.sinks);
+
+ auto voice_dsp_states{allocator.Allocate<VoiceState>(params.voices, 0x40)};
+ if (voice_dsp_states.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ for (auto& voice_state : voice_dsp_states) {
+ voice_state = {};
+ }
+
+ voice_context.Initialize(sorted_voice_infos, voice_infos, voice_channel_resources,
+ voice_cpu_states, voice_dsp_states, params.voices);
+
+ if (params.perf_frames > 0) {
+ const auto perf_workbuffer_size{
+ PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior,
+ params) *
+ (params.perf_frames + 1) +
+ 0xC};
+ performance_workbuffer = allocator.Allocate<u8>(perf_workbuffer_size, 0x40);
+ if (performance_workbuffer.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+ std::memset(performance_workbuffer.data(), 0, performance_workbuffer.size_bytes());
+ performance_manager.Initialize(performance_workbuffer, performance_workbuffer.size_bytes(),
+ params, behavior, memory_pool_info);
+ }
+
+ render_time_limit_percent = 100;
+ drop_voice = params.voice_drop_enabled && params.execution_mode == ExecutionMode::Auto;
+
+ allocator.Align(0x40);
+ command_workbuffer_size = allocator.GetRemainingSize();
+ command_workbuffer = allocator.Allocate<u8>(command_workbuffer_size, 0x40);
+ if (command_workbuffer.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ command_buffer_size = 0;
+ reset_command_buffers = true;
+
+ // nn::audio::dsp::FlushDataCache(transferMemory, transferMemorySize);
+
+ if (behavior.IsCommandProcessingTimeEstimatorVersion5Supported()) {
+ command_processing_time_estimator =
+ std::make_unique<CommandProcessingTimeEstimatorVersion5>(sample_count,
+ mix_buffer_count);
+ } else if (behavior.IsCommandProcessingTimeEstimatorVersion4Supported()) {
+ command_processing_time_estimator =
+ std::make_unique<CommandProcessingTimeEstimatorVersion4>(sample_count,
+ mix_buffer_count);
+ } else if (behavior.IsCommandProcessingTimeEstimatorVersion3Supported()) {
+ command_processing_time_estimator =
+ std::make_unique<CommandProcessingTimeEstimatorVersion3>(sample_count,
+ mix_buffer_count);
+ } else if (behavior.IsCommandProcessingTimeEstimatorVersion2Supported()) {
+ command_processing_time_estimator =
+ std::make_unique<CommandProcessingTimeEstimatorVersion2>(sample_count,
+ mix_buffer_count);
+ } else {
+ command_processing_time_estimator =
+ std::make_unique<CommandProcessingTimeEstimatorVersion1>(sample_count,
+ mix_buffer_count);
+ }
+
+ initialized = true;
+ return ResultSuccess;
+}
+
+void System::Finalize() {
+ if (!initialized) {
+ return;
+ }
+
+ if (active) {
+ Stop();
+ }
+
+ applet_resource_user_id = 0;
+
+ PoolMapper pool_mapper(process_handle, false);
+ pool_mapper.Unmap(memory_pool_info);
+
+ if (process_handle) {
+ pool_mapper.ClearUseState(memory_pool_workbuffer, memory_pool_count);
+ for (auto& memory_pool : memory_pool_workbuffer) {
+ if (memory_pool.IsMapped()) {
+ pool_mapper.Unmap(memory_pool);
+ }
+ }
+
+ // dsp::ProcessCleanup
+ // close handle
+ }
+ initialized = false;
+}
+
+void System::Start() {
+ std::scoped_lock l{lock};
+ frames_elapsed = 0;
+ state = State::Started;
+ active = true;
+}
+
+void System::Stop() {
+ {
+ std::scoped_lock l{lock};
+ state = State::Stopped;
+ active = false;
+ }
+
+ if (execution_mode == ExecutionMode::Auto) {
+ // Should wait for the system to terminate here, but core timing (should have) already
+ // stopped, so this isn't needed. Find a way to make this definite.
+
+ // terminate_event.Wait();
+ }
+}
+
+Result System::Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output) {
+ std::scoped_lock l{lock};
+
+ const auto start_time{core.CoreTiming().GetClockTicks()};
+
+ InfoUpdater info_updater(input, output, process_handle, behavior);
+
+ auto result{info_updater.UpdateBehaviorInfo(behavior)};
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update BehaviorInfo!");
+ return result;
+ }
+
+ result = info_updater.UpdateMemoryPools(memory_pool_workbuffer, memory_pool_count);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update MemoryPools!");
+ return result;
+ }
+
+ result = info_updater.UpdateVoiceChannelResources(voice_context);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update VoiceChannelResources!");
+ return result;
+ }
+
+ result = info_updater.UpdateVoices(voice_context, memory_pool_workbuffer, memory_pool_count);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update Voices!");
+ return result;
+ }
+
+ result = info_updater.UpdateEffects(effect_context, active, memory_pool_workbuffer,
+ memory_pool_count);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update Effects!");
+ return result;
+ }
+
+ if (behavior.IsSplitterSupported()) {
+ result = info_updater.UpdateSplitterInfo(splitter_context);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update SplitterInfo!");
+ return result;
+ }
+ }
+
+ result =
+ info_updater.UpdateMixes(mix_context, mix_buffer_count, effect_context, splitter_context);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update Mixes!");
+ return result;
+ }
+
+ result = info_updater.UpdateSinks(sink_context, memory_pool_workbuffer, memory_pool_count);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update Sinks!");
+ return result;
+ }
+
+ PerformanceManager* perf_manager{nullptr};
+ if (performance_manager.IsInitialized()) {
+ perf_manager = &performance_manager;
+ }
+
+ result =
+ info_updater.UpdatePerformanceBuffer(performance, performance.size_bytes(), perf_manager);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update PerformanceBuffer!");
+ return result;
+ }
+
+ result = info_updater.UpdateErrorInfo(behavior);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update ErrorInfo!");
+ return result;
+ }
+
+ if (behavior.IsElapsedFrameCountSupported()) {
+ result = info_updater.UpdateRendererInfo(frames_elapsed);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update RendererInfo!");
+ return result;
+ }
+ }
+
+ result = info_updater.CheckConsumedSize();
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Invalid consume size!");
+ return result;
+ }
+
+ adsp_rendered_event->GetWritableEvent().Clear();
+ num_times_updated++;
+
+ const auto end_time{core.CoreTiming().GetClockTicks()};
+ ticks_spent_updating += end_time - start_time;
+
+ return ResultSuccess;
+}
+
+u32 System::GetRenderingTimeLimit() const {
+ return render_time_limit_percent;
+}
+
+void System::SetRenderingTimeLimit(const u32 limit) {
+ render_time_limit_percent = limit;
+}
+
+u32 System::GetSessionId() const {
+ return session_id;
+}
+
+u32 System::GetSampleRate() const {
+ return sample_rate;
+}
+
+u32 System::GetSampleCount() const {
+ return sample_count;
+}
+
+u32 System::GetMixBufferCount() const {
+ return mix_buffer_count;
+}
+
+ExecutionMode System::GetExecutionMode() const {
+ return execution_mode;
+}
+
+u32 System::GetRenderingDevice() const {
+ return render_device;
+}
+
+bool System::IsActive() const {
+ return active;
+}
+
+void System::SendCommandToDsp() {
+ std::scoped_lock l{lock};
+
+ if (initialized) {
+ if (active) {
+ terminate_event.Reset();
+ const auto remaining_command_count{adsp.GetRemainCommandCount(session_id)};
+ u64 command_size{0};
+
+ if (remaining_command_count) {
+ adsp_behind = true;
+ command_size = command_buffer_size;
+ } else {
+ command_size = GenerateCommand(command_workbuffer, command_workbuffer_size);
+ }
+
+ auto translated_addr{
+ memory_pool_info.Translate(CpuAddr(command_workbuffer.data()), command_size)};
+
+ auto time_limit_percent{70.0f};
+ if (behavior.IsAudioRendererProcessingTimeLimit80PercentSupported()) {
+ time_limit_percent = 80.0f;
+ } else if (behavior.IsAudioRendererProcessingTimeLimit75PercentSupported()) {
+ time_limit_percent = 75.0f;
+ } else {
+ // result ignored and 70 is used anyway
+ behavior.IsAudioRendererProcessingTimeLimit70PercentSupported();
+ time_limit_percent = 70.0f;
+ }
+
+ ADSP::CommandBuffer command_buffer{
+ .buffer{translated_addr},
+ .size{command_size},
+ .time_limit{
+ static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
+ (static_cast<f32>(render_time_limit_percent) / 100.0f))},
+ .remaining_command_count{remaining_command_count},
+ .reset_buffers{reset_command_buffers},
+ .applet_resource_user_id{applet_resource_user_id},
+ .render_time_taken{adsp.GetRenderTimeTaken(session_id)},
+ };
+
+ adsp.SendCommandBuffer(session_id, command_buffer);
+ reset_command_buffers = false;
+ command_buffer_size = command_size;
+ if (remaining_command_count == 0) {
+ adsp_rendered_event->GetWritableEvent().Signal();
+ }
+ } else {
+ adsp.ClearRemainCount(session_id);
+ terminate_event.Set();
+ }
+ }
+}
+
+u64 System::GenerateCommand(std::span<u8> in_command_buffer,
+ [[maybe_unused]] const u64 command_buffer_size_) {
+ PoolMapper::ClearUseState(memory_pool_workbuffer, memory_pool_count);
+ const auto start_time{core.CoreTiming().GetClockTicks()};
+
+ auto command_list_header{reinterpret_cast<CommandListHeader*>(in_command_buffer.data())};
+
+ command_list_header->buffer_count = static_cast<s16>(voice_channels + mix_buffer_count);
+ command_list_header->sample_count = sample_count;
+ command_list_header->sample_rate = sample_rate;
+ command_list_header->samples_buffer = samples_workbuffer;
+
+ const auto performance_initialized{performance_manager.IsInitialized()};
+ if (performance_initialized) {
+ performance_manager.TapFrame(adsp_behind, num_voices_dropped, render_start_tick);
+ adsp_behind = false;
+ num_voices_dropped = 0;
+ render_start_tick = 0;
+ }
+
+ s8 channel_count{2};
+ if (execution_mode == ExecutionMode::Auto) {
+ const auto& sink{core.AudioCore().GetOutputSink()};
+ channel_count = static_cast<s8>(sink.GetDeviceChannels());
+ }
+
+ AudioRendererSystemContext render_context{
+ .session_id{session_id},
+ .channels{channel_count},
+ .mix_buffer_count{mix_buffer_count},
+ .behavior{&behavior},
+ .depop_buffer{depop_buffer},
+ .upsampler_manager{upsampler_manager},
+ .memory_pool_info{&memory_pool_info},
+ };
+
+ CommandBuffer command_buffer{
+ .command_list{in_command_buffer},
+ .sample_count{sample_count},
+ .sample_rate{sample_rate},
+ .size{sizeof(CommandListHeader)},
+ .count{0},
+ .estimated_process_time{0},
+ .memory_pool{&memory_pool_info},
+ .time_estimator{command_processing_time_estimator.get()},
+ .behavior{&behavior},
+ };
+
+ PerformanceManager* perf_manager{nullptr};
+ if (performance_initialized) {
+ perf_manager = &performance_manager;
+ }
+
+ CommandGenerator command_generator{command_buffer, *command_list_header, render_context,
+ voice_context, mix_context, effect_context,
+ sink_context, splitter_context, perf_manager};
+
+ voice_context.SortInfo();
+
+ const auto start_estimated_time{command_buffer.estimated_process_time};
+
+ command_generator.GenerateVoiceCommands();
+ command_generator.GenerateSubMixCommands();
+ command_generator.GenerateFinalMixCommands();
+ command_generator.GenerateSinkCommands();
+
+ if (drop_voice) {
+ f32 time_limit_percent{70.0f};
+ if (render_context.behavior->IsAudioRendererProcessingTimeLimit80PercentSupported()) {
+ time_limit_percent = 80.0f;
+ } else if (render_context.behavior
+ ->IsAudioRendererProcessingTimeLimit75PercentSupported()) {
+ time_limit_percent = 75.0f;
+ } else {
+ // result is ignored
+ render_context.behavior->IsAudioRendererProcessingTimeLimit70PercentSupported();
+ time_limit_percent = 70.0f;
+ }
+ const auto time_limit{static_cast<u32>(
+ static_cast<f32>(start_estimated_time - command_buffer.estimated_process_time) +
+ (((time_limit_percent / 100.0f) * 2'880'000.0) *
+ (static_cast<f32>(render_time_limit_percent) / 100.0f)))};
+ num_voices_dropped = DropVoices(command_buffer, start_estimated_time, time_limit);
+ }
+
+ command_list_header->buffer_size = command_buffer.size;
+ command_list_header->command_count = command_buffer.count;
+
+ voice_context.UpdateStateByDspShared();
+
+ if (render_context.behavior->IsEffectInfoVersion2Supported()) {
+ effect_context.UpdateStateByDspShared();
+ }
+
+ const auto end_time{core.CoreTiming().GetClockTicks()};
+ total_ticks_elapsed += end_time - start_time;
+ num_command_lists_generated++;
+ render_start_tick = adsp.GetRenderingStartTick(session_id);
+ frames_elapsed++;
+
+ return command_buffer.size;
+}
+
+u32 System::DropVoices(CommandBuffer& command_buffer, const u32 estimated_process_time,
+ const u32 time_limit) {
+ u32 i{0};
+ auto command_list{command_buffer.command_list.data() + sizeof(CommandListHeader)};
+ ICommand* cmd{};
+
+ for (; i < command_buffer.count; i++) {
+ cmd = reinterpret_cast<ICommand*>(command_list);
+ if (cmd->type != CommandId::Performance &&
+ cmd->type != CommandId::DataSourcePcmInt16Version1 &&
+ cmd->type != CommandId::DataSourcePcmInt16Version2 &&
+ cmd->type != CommandId::DataSourcePcmFloatVersion1 &&
+ cmd->type != CommandId::DataSourcePcmFloatVersion2 &&
+ cmd->type != CommandId::DataSourceAdpcmVersion1 &&
+ cmd->type != CommandId::DataSourceAdpcmVersion2) {
+ break;
+ }
+ command_list += cmd->size;
+ }
+
+ if (cmd == nullptr || command_buffer.count == 0 || i >= command_buffer.count) {
+ return 0;
+ }
+
+ auto voices_dropped{0};
+ while (i < command_buffer.count) {
+ const auto node_id{cmd->node_id};
+ const auto node_id_type{cmd->node_id >> 28};
+ const auto node_id_base{cmd->node_id & 0xFFF};
+
+ if (estimated_process_time <= time_limit) {
+ break;
+ }
+
+ if (node_id_type != 1) {
+ break;
+ }
+
+ auto& voice_info{voice_context.GetInfo(node_id_base)};
+ if (voice_info.priority == HighestVoicePriority) {
+ break;
+ }
+
+ voices_dropped++;
+ voice_info.voice_dropped = true;
+
+ if (i < command_buffer.count) {
+ while (cmd->node_id == node_id) {
+ if (cmd->type == CommandId::DepopPrepare) {
+ cmd->enabled = true;
+ } else if (cmd->type == CommandId::Performance || !cmd->enabled) {
+ cmd->enabled = false;
+ }
+ i++;
+ command_list += cmd->size;
+ cmd = reinterpret_cast<ICommand*>(command_list);
+ }
+ }
+ }
+ return voices_dropped;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/system.h b/src/audio_core/renderer/system.h
new file mode 100644
index 000000000..bcbe65b07
--- /dev/null
+++ b/src/audio_core/renderer/system.h
@@ -0,0 +1,307 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <span>
+
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/command/command_processing_time_estimator.h"
+#include "audio_core/renderer/effect/effect_context.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "audio_core/renderer/mix/mix_context.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "audio_core/renderer/sink/sink_context.h"
+#include "audio_core/renderer/splitter/splitter_context.h"
+#include "audio_core/renderer/upsampler/upsampler_manager.h"
+#include "audio_core/renderer/voice/voice_context.h"
+#include "common/thread.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace Core {
+namespace Memory {
+class Memory;
+}
+class System;
+} // namespace Core
+
+namespace Kernel {
+class KEvent;
+class KTransferMemory;
+} // namespace Kernel
+
+namespace AudioCore {
+struct AudioRendererParameterInternal;
+
+namespace AudioRenderer {
+class CommandBuffer;
+namespace ADSP {
+class ADSP;
+}
+
+/**
+ * Audio Renderer System, the main worker for audio rendering.
+ */
+class System {
+ enum class State {
+ Started = 0,
+ Stopped = 2,
+ };
+
+public:
+ explicit System(Core::System& core, Kernel::KEvent* adsp_rendered_event);
+
+ /**
+ * Calculate the total size required for all audio render workbuffers.
+ *
+ * @param params - Input parameters with the numbers of voices/mixes/sinks/etc.
+ * @return Size (in bytes) required for the audio renderer.
+ */
+ static u64 GetWorkBufferSize(const AudioRendererParameterInternal& params);
+
+ /**
+ * Initialize the renderer system.
+ * Allocates workbuffers and initializes everything to a default state, ready to receive a
+ * RequestUpdate.
+ *
+ * @param params - Input parameters to initialize the system with.
+ * @param transfer_memory - Game-supplied memory for all workbuffers. Unused.
+ * @param transfer_memory_size - Size of the transfer memory. Unused.
+ * @param process_handle - Process handle, also used for memory. Unused.
+ * @param applet_resource_user_id - Applet id for this renderer. Unused.
+ * @param session_id - Session id of this renderer.
+ * @return Result code.
+ */
+ Result Initialize(const AudioRendererParameterInternal& params,
+ Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
+ u32 process_handle, u64 applet_resource_user_id, s32 session_id);
+
+ /**
+ * Finalize the system.
+ */
+ void Finalize();
+
+ /**
+ * Start the system.
+ */
+ void Start();
+
+ /**
+ * Stop the system.
+ */
+ void Stop();
+
+ /**
+ * Update the system.
+ *
+ * @param input - Inout buffer containing the update data.
+ * @param performance - Optional buffer for writing back performance metrics.
+ * @param output - Output information from rendering.
+ * @return Result code.
+ */
+ Result Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output);
+
+ /**
+ * Get the time limit (percent) for rendering
+ *
+ * @return Time limit as a percent.
+ */
+ u32 GetRenderingTimeLimit() const;
+
+ /**
+ * Set the time limit (percent) for rendering
+ *
+ * @param limit - New time limit.
+ */
+ void SetRenderingTimeLimit(u32 limit);
+
+ /**
+ * Get the session id for this system.
+ *
+ * @return Session id of this system.
+ */
+ u32 GetSessionId() const;
+
+ /**
+ * Get the sample rate of this system.
+ *
+ * @return Sample rate of this system.
+ */
+ u32 GetSampleRate() const;
+
+ /**
+ * Get the sample count of this system.
+ *
+ * @return Sample count of this system.
+ */
+ u32 GetSampleCount() const;
+
+ /**
+ * Get the number of mix buffers for this system.
+ *
+ * @return Number of mix buffers in the system.
+ */
+ u32 GetMixBufferCount() const;
+
+ /**
+ * Get the execution mode of this system.
+ * Note: Only Auto is implemented.
+ *
+ * @return Execution mode for this system.
+ */
+ ExecutionMode GetExecutionMode() const;
+
+ /**
+ * Get the rendering deivce for this system.
+ * This is unused.
+ *
+ * @return Rendering device for this system.
+ */
+ u32 GetRenderingDevice() const;
+
+ /**
+ * Check if this system is currently active.
+ *
+ * @return True if active, otherwise false.
+ */
+ bool IsActive() const;
+
+ /**
+ * Prepare and generate a list of commands for the AudioRenderer based on current state,
+ * signalling the buffer event when all processed.
+ */
+ void SendCommandToDsp();
+
+ /**
+ * Generate a list of commands for the AudioRenderer based on current state.
+ *
+ * @param command_buffer - Buffer for commands to be written to.
+ * @param command_buffer_size - Size of the command_buffer.
+ *
+ * @return Number of bytes written.
+ */
+ u64 GenerateCommand(std::span<u8> command_buffer, u64 command_buffer_size);
+
+ /**
+ * Try to drop some voices if the AudioRenderer fell behind.
+ *
+ * @param command_buffer - Command buffer to drop voices from.
+ * @param estimated_process_time - Current estimated processing time of all commands.
+ * @param time_limit - Time limit for rendering, voices are dropped if estimated
+ * exceeds this.
+ *
+ * @return Number of voices dropped.
+ */
+ u32 DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time, u32 time_limit);
+
+private:
+ /// Core system
+ Core::System& core;
+ /// Reference to the ADSP for communication
+ ADSP::ADSP& adsp;
+ /// Is this system initialized?
+ bool initialized{};
+ /// Is this system currently active?
+ std::atomic<bool> active{};
+ /// State of the system
+ State state{State::Stopped};
+ /// Sample rate for the system
+ u32 sample_rate{};
+ /// Sample count of the system
+ u32 sample_count{};
+ /// Number of mix buffers in use by the system
+ s16 mix_buffer_count{};
+ /// Workbuffer for mix buffers, used by the AudioRenderer
+ std::span<s32> samples_workbuffer{};
+ /// Depop samples for depopping commands
+ std::span<s32> depop_buffer{};
+ /// Number of memory pools in the buffer
+ u32 memory_pool_count{};
+ /// Workbuffer for memory pools
+ std::span<MemoryPoolInfo> memory_pool_workbuffer{};
+ /// System memory pool info
+ MemoryPoolInfo memory_pool_info{};
+ /// Workbuffer that commands will be generated into
+ std::span<u8> command_workbuffer{};
+ /// Size of command workbuffer
+ u64 command_workbuffer_size{};
+ /// Numebr of commands in the workbuffer
+ u64 command_buffer_size{};
+ /// Manager for upsamplers
+ UpsamplerManager* upsampler_manager{};
+ /// Upsampler workbuffer
+ std::span<UpsamplerInfo> upsampler_infos{};
+ /// Number of upsamplers in the workbuffer
+ u32 upsampler_count{};
+ /// Holds and controls all voices
+ VoiceContext voice_context{};
+ /// Holds and controls all mixes
+ MixContext mix_context{};
+ /// Holds and controls all effects
+ EffectContext effect_context{};
+ /// Holds and controls all sinks
+ SinkContext sink_context{};
+ /// Holds and controls all splitters
+ SplitterContext splitter_context{};
+ /// Estimates the time taken for each command
+ std::unique_ptr<ICommandProcessingTimeEstimator> command_processing_time_estimator{};
+ /// Session id of this system
+ s32 session_id{};
+ /// Number of channels in use by voices
+ s32 voice_channels{};
+ /// Event to be called when the AudioRenderer processes a command list
+ Kernel::KEvent* adsp_rendered_event{};
+ /// Event signalled on system terminate
+ Common::Event terminate_event{};
+ /// Does what locks do
+ std::mutex lock{};
+ /// Handle for the process for this system, unused
+ u32 process_handle{};
+ /// Applet resource id for this system, unused
+ u64 applet_resource_user_id{};
+ /// Controls performance input and output
+ PerformanceManager performance_manager{};
+ /// Workbuffer for performance metrics
+ std::span<u8> performance_workbuffer{};
+ /// Main workbuffer, from which all other workbuffers here allocate into
+ std::unique_ptr<u8[]> workbuffer{};
+ /// Size of the main workbuffer
+ u64 workbuffer_size{};
+ /// Unknown buffer/marker
+ std::span<u8> unk_2A8{};
+ /// Size of the above unknown buffer/marker
+ u64 unk_2B0{};
+ /// Rendering time limit (percent)
+ u32 render_time_limit_percent{};
+ /// Should any voices be dropped?
+ bool drop_voice{};
+ /// Should the backend stream have its buffers flushed?
+ bool reset_command_buffers{};
+ /// Execution mode of this system, only Auto is supported
+ ExecutionMode execution_mode{ExecutionMode::Auto};
+ /// Render device, unused
+ u32 render_device{};
+ /// Behaviour to check which features are supported by the user revision
+ BehaviorInfo behavior{};
+ /// Total ticks the audio system has been running
+ u64 total_ticks_elapsed{};
+ /// Ticks the system has spent in updates
+ u64 ticks_spent_updating{};
+ /// Number of times a command list was generated
+ u64 num_command_lists_generated{};
+ /// Number of times the system has updated
+ u64 num_times_updated{};
+ /// Number of frames generated, written back to the game
+ std::atomic<u64> frames_elapsed{};
+ /// Is the AudioRenderer running too slow?
+ bool adsp_behind{};
+ /// Number of voices dropped
+ u32 num_voices_dropped{};
+ /// Tick that rendering started
+ u64 render_start_tick{};
+};
+
+} // namespace AudioRenderer
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp
new file mode 100644
index 000000000..f66b2b890
--- /dev/null
+++ b/src/audio_core/renderer/system_manager.cpp
@@ -0,0 +1,126 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <chrono>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/renderer/adsp/adsp.h"
+#include "audio_core/renderer/system_manager.h"
+#include "common/microprofile.h"
+#include "common/thread.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+
+MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
+ MP_RGB(60, 19, 97));
+
+namespace AudioCore::AudioRenderer {
+constexpr std::chrono::nanoseconds RENDER_TIME{5'000'000UL};
+
+SystemManager::SystemManager(Core::System& core_)
+ : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
+ thread_event{Core::Timing::CreateEvent(
+ "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
+ return ThreadFunc2(time);
+ })} {}
+
+SystemManager::~SystemManager() {
+ Stop();
+}
+
+bool SystemManager::InitializeUnsafe() {
+ if (!active) {
+ if (adsp.Start()) {
+ active = true;
+ thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); });
+ core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), RENDER_TIME,
+ thread_event);
+ }
+ }
+
+ return adsp.GetState() == ADSP::State::Started;
+}
+
+void SystemManager::Stop() {
+ if (!active) {
+ return;
+ }
+ core.CoreTiming().UnscheduleEvent(thread_event, {});
+ active = false;
+ update.store(true);
+ update.notify_all();
+ thread.join();
+ adsp.Stop();
+}
+
+bool SystemManager::Add(System& system_) {
+ std::scoped_lock l2{mutex2};
+
+ if (systems.size() + 1 > MaxRendererSessions) {
+ LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!");
+ return false;
+ }
+
+ {
+ std::scoped_lock l{mutex1};
+ if (systems.empty()) {
+ if (!InitializeUnsafe()) {
+ LOG_ERROR(Service_Audio, "Failed to start the AudioRenderer SystemManager");
+ return false;
+ }
+ }
+ }
+
+ systems.push_back(&system_);
+ return true;
+}
+
+bool SystemManager::Remove(System& system_) {
+ std::scoped_lock l2{mutex2};
+
+ {
+ std::scoped_lock l{mutex1};
+ if (systems.remove(&system_) == 0) {
+ LOG_ERROR(Service_Audio,
+ "Failed to remove a render system, it was not found in the list!");
+ return false;
+ }
+ }
+
+ if (systems.empty()) {
+ Stop();
+ }
+ return true;
+}
+
+void SystemManager::ThreadFunc() {
+ constexpr char name[]{"AudioRenderSystemManager"};
+ MicroProfileOnThreadCreate(name);
+ Common::SetCurrentThreadName(name);
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
+ while (active) {
+ {
+ std::scoped_lock l{mutex1};
+
+ MICROPROFILE_SCOPE(Audio_RenderSystemManager);
+
+ for (auto system : systems) {
+ system->SendCommandToDsp();
+ }
+ }
+
+ adsp.Signal();
+ adsp.Wait();
+
+ update.wait(false);
+ update.store(false);
+ }
+}
+
+std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) {
+ update.store(true);
+ update.notify_all();
+ return std::nullopt;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h
new file mode 100644
index 000000000..81457a3a1
--- /dev/null
+++ b/src/audio_core/renderer/system_manager.h
@@ -0,0 +1,104 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <list>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <thread>
+
+#include "audio_core/renderer/system.h"
+
+namespace Core {
+namespace Timing {
+struct EventType;
+}
+class System;
+} // namespace Core
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class ADSP;
+class AudioRenderer_Mailbox;
+} // namespace ADSP
+
+/**
+ * Manages all audio renderers, responsible for triggering command list generation and signalling
+ * the ADSP.
+ */
+class SystemManager {
+public:
+ explicit SystemManager(Core::System& core);
+ ~SystemManager();
+
+ /**
+ * Initialize the system manager, called when any system is registered.
+ *
+ * @return True if sucessfully initialized, otherwise false.
+ */
+ bool InitializeUnsafe();
+
+ /**
+ * Stop the system manager.
+ */
+ void Stop();
+
+ /**
+ * Add an audio render system to the manager.
+ * The manager does not own the system, so do not free it without calling Remove.
+ *
+ * @param system - The system to add.
+ * @return True if succesfully added, otherwise false.
+ */
+ bool Add(System& system);
+
+ /**
+ * Remove an audio render system from the manager.
+ *
+ * @param system - The system to remove.
+ * @return True if succesfully removed, otherwise false.
+ */
+ bool Remove(System& system);
+
+private:
+ /**
+ * Main thread responsible for command generation.
+ */
+ void ThreadFunc();
+
+ /**
+ * Signalling core timing thread to run ThreadFunc.
+ */
+ std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time);
+
+ enum class StreamState {
+ Filling,
+ Steady,
+ Draining,
+ };
+
+ /// Core system
+ Core::System& core;
+ /// List of pointers to managed systems
+ std::list<System*> systems{};
+ /// Main worker thread for generating command lists
+ std::jthread thread;
+ /// Mutex for the systems
+ std::mutex mutex1{};
+ /// Mutex for adding/removing systems
+ std::mutex mutex2{};
+ /// Is the system manager thread active?
+ std::atomic<bool> active{};
+ /// Reference to the ADSP for communication
+ ADSP::ADSP& adsp;
+ /// AudioRenderer mailbox for communication
+ ADSP::AudioRenderer_Mailbox* mailbox{};
+ /// Core timing event to signal main thread
+ std::shared_ptr<Core::Timing::EventType> thread_event;
+ /// Atomic for main thread to wait on
+ std::atomic<bool> update{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_info.cpp b/src/audio_core/renderer/upsampler/upsampler_info.cpp
new file mode 100644
index 000000000..e3d2f7db0
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_info.cpp
@@ -0,0 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/upsampler/upsampler_info.h"
+
+namespace AudioCore::AudioRenderer {} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_info.h b/src/audio_core/renderer/upsampler/upsampler_info.h
new file mode 100644
index 000000000..a43c15af3
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_info.h
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/upsampler/upsampler_state.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class UpsamplerManager;
+
+/**
+ * Manages information needed to upsample a mix buffer.
+ */
+struct UpsamplerInfo {
+ /// States used by the AudioRenderer across calls.
+ std::array<UpsamplerState, MaxChannels> states{};
+ /// Pointer to the manager
+ UpsamplerManager* manager{};
+ /// Pointer to the samples to be upsampled
+ CpuAddr samples_pos{};
+ /// Target number of samples to upsample to
+ u32 sample_count{};
+ /// Number of channels to upsample
+ u32 input_count{};
+ /// Is this upsampler enabled?
+ bool enabled{};
+ /// Mix buffer indexes to be upsampled
+ std::array<s16, MaxChannels> inputs{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.cpp b/src/audio_core/renderer/upsampler/upsampler_manager.cpp
new file mode 100644
index 000000000..4c76a5066
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_manager.cpp
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/upsampler/upsampler_manager.h"
+
+namespace AudioCore::AudioRenderer {
+
+UpsamplerManager::UpsamplerManager(const u32 count_, std::span<UpsamplerInfo> infos_,
+ std::span<s32> workbuffer_)
+ : count{count_}, upsampler_infos{infos_}, workbuffer{workbuffer_} {}
+
+UpsamplerInfo* UpsamplerManager::Allocate() {
+ std::scoped_lock l{lock};
+
+ if (count == 0) {
+ return nullptr;
+ }
+
+ u32 free_index{0};
+ for (auto& upsampler : upsampler_infos) {
+ if (!upsampler.enabled) {
+ break;
+ }
+ free_index++;
+ }
+
+ if (free_index >= count) {
+ return nullptr;
+ }
+
+ auto& upsampler{upsampler_infos[free_index]};
+ upsampler.manager = this;
+ upsampler.sample_count = TargetSampleCount;
+ upsampler.samples_pos = CpuAddr(&workbuffer[upsampler.sample_count * MaxChannels]);
+ upsampler.enabled = true;
+ return &upsampler;
+}
+
+void UpsamplerManager::Free(UpsamplerInfo* info) {
+ std::scoped_lock l{lock};
+ info->enabled = false;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h
new file mode 100644
index 000000000..83c697c0c
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_manager.h
@@ -0,0 +1,45 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+#include <span>
+
+#include "audio_core/renderer/upsampler/upsampler_info.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Manages and has utility functions for upsampler infos.
+ */
+class UpsamplerManager {
+public:
+ UpsamplerManager(u32 count, std::span<UpsamplerInfo> infos, std::span<s32> workbuffer);
+
+ /**
+ * Allocate a new UpsamplerInfo.
+ *
+ * @return The allocated upsampler, may be nullptr if alloc failed.
+ */
+ UpsamplerInfo* Allocate();
+
+ /**
+ * Free the given upsampler.
+ *
+ * @param info The upsampler to be freed.
+ */
+ void Free(UpsamplerInfo* info);
+
+private:
+ /// Maximum number of upsamplers in the buffer
+ const u32 count;
+ /// Upsamplers buffer
+ std::span<UpsamplerInfo> upsampler_infos;
+ /// Workbuffer for upsampling samples
+ std::span<s32> workbuffer;
+ /// Lock for allocate/free
+ std::mutex lock{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_state.h b/src/audio_core/renderer/upsampler/upsampler_state.h
new file mode 100644
index 000000000..28cebe200
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_state.h
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Upsampling state used by the AudioRenderer across calls.
+ */
+struct UpsamplerState {
+ static constexpr u16 HistorySize = 20;
+
+ /// Source data to target data ratio. E.g 48'000/32'000 = 1.5
+ Common::FixedPoint<16, 16> ratio;
+ /// Sample history
+ std::array<Common::FixedPoint<24, 8>, HistorySize> history;
+ /// Size of the sinc coefficient window
+ u16 window_size;
+ /// Read index for the history
+ u16 history_output_index;
+ /// Write index for the history
+ u16 history_input_index;
+ /// Start offset within the history, fixed to 0
+ u16 history_start_index;
+ /// Ebd offset within the history, fixed to HistorySize
+ u16 history_end_index;
+ /// Is this state initialized?
+ bool initialized;
+ /// Index of the current sample.
+ /// E.g 16K -> 48K has a ratio of 3, so this will be 0-2.
+ /// See the Upsample command in the AudioRenderer for more information.
+ u8 sample_index;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_channel_resource.h b/src/audio_core/renderer/voice/voice_channel_resource.h
new file mode 100644
index 000000000..26ab4ccce
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_channel_resource.h
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Represents one channel for mixing a voice.
+ */
+class VoiceChannelResource {
+public:
+ struct InParameter {
+ /* 0x00 */ u32 id;
+ /* 0x04 */ std::array<f32, MaxMixBuffers> mix_volumes;
+ /* 0x64 */ bool in_use;
+ /* 0x65 */ char unk65[0xB];
+ };
+ static_assert(sizeof(InParameter) == 0x70,
+ "VoiceChannelResource::InParameter has the wrong size!");
+
+ explicit VoiceChannelResource(u32 id_) : id{id_} {}
+
+ /// Current volume for each mix buffer
+ std::array<f32, MaxMixBuffers> mix_volumes{};
+ /// Previous volume for each mix buffer
+ std::array<f32, MaxMixBuffers> prev_mix_volumes{};
+ /// Id of this resource
+ const u32 id;
+ /// Is this resource in use?
+ bool in_use{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_context.cpp b/src/audio_core/renderer/voice/voice_context.cpp
new file mode 100644
index 000000000..eafb51b01
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_context.cpp
@@ -0,0 +1,86 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <ranges>
+
+#include "audio_core/renderer/voice/voice_context.h"
+
+namespace AudioCore::AudioRenderer {
+
+VoiceState& VoiceContext::GetDspSharedState(const u32 index) {
+ if (index >= dsp_states.size()) {
+ LOG_ERROR(Service_Audio, "Invalid voice dsp state index {:04X}", index);
+ }
+ return dsp_states[index];
+}
+
+VoiceChannelResource& VoiceContext::GetChannelResource(const u32 index) {
+ if (index >= channel_resources.size()) {
+ LOG_ERROR(Service_Audio, "Invalid voice channel resource index {:04X}", index);
+ }
+ return channel_resources[index];
+}
+
+void VoiceContext::Initialize(std::span<VoiceInfo*> sorted_voice_infos_,
+ std::span<VoiceInfo> voice_infos_,
+ std::span<VoiceChannelResource> voice_channel_resources_,
+ std::span<VoiceState> cpu_states_, std::span<VoiceState> dsp_states_,
+ const u32 voice_count_) {
+ sorted_voice_info = sorted_voice_infos_;
+ voices = voice_infos_;
+ channel_resources = voice_channel_resources_;
+ cpu_states = cpu_states_;
+ dsp_states = dsp_states_;
+ voice_count = voice_count_;
+ active_count = 0;
+}
+
+VoiceInfo* VoiceContext::GetSortedInfo(const u32 index) {
+ if (index >= sorted_voice_info.size()) {
+ LOG_ERROR(Service_Audio, "Invalid voice sorted info index {:04X}", index);
+ }
+ return sorted_voice_info[index];
+}
+
+VoiceInfo& VoiceContext::GetInfo(const u32 index) {
+ if (index >= voices.size()) {
+ LOG_ERROR(Service_Audio, "Invalid voice info index {:04X}", index);
+ }
+ return voices[index];
+}
+
+VoiceState& VoiceContext::GetState(const u32 index) {
+ if (index >= cpu_states.size()) {
+ LOG_ERROR(Service_Audio, "Invalid voice cpu state index {:04X}", index);
+ }
+ return cpu_states[index];
+}
+
+u32 VoiceContext::GetCount() const {
+ return voice_count;
+}
+
+u32 VoiceContext::GetActiveCount() const {
+ return active_count;
+}
+
+void VoiceContext::SetActiveCount(const u32 active_count_) {
+ active_count = active_count_;
+}
+
+void VoiceContext::SortInfo() {
+ for (u32 i = 0; i < voice_count; i++) {
+ sorted_voice_info[i] = &voices[i];
+ }
+
+ std::ranges::sort(sorted_voice_info, [](const VoiceInfo* a, const VoiceInfo* b) {
+ return a->priority != b->priority ? a->priority < b->priority
+ : a->sort_order < b->sort_order;
+ });
+}
+
+void VoiceContext::UpdateStateByDspShared() {
+ std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState));
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_context.h b/src/audio_core/renderer/voice/voice_context.h
new file mode 100644
index 000000000..43b677154
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_context.h
@@ -0,0 +1,126 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/voice/voice_channel_resource.h"
+#include "audio_core/renderer/voice/voice_info.h"
+#include "audio_core/renderer/voice/voice_state.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Contains all voices, with utility functions for managing them.
+ */
+class VoiceContext {
+public:
+ /**
+ * Get the AudioRenderer state for a given index
+ *
+ * @param index - State index to get.
+ * @return The requested voice state.
+ */
+ VoiceState& GetDspSharedState(u32 index);
+
+ /**
+ * Get the channel resource for a given index
+ *
+ * @param index - Resource index to get.
+ * @return The requested voice resource.
+ */
+ VoiceChannelResource& GetChannelResource(u32 index);
+
+ /**
+ * Initialize the voice context.
+ *
+ * @param sorted_voice_infos - Workbuffer for the sorted voices.
+ * @param voice_infos - Workbuffer for the voices.
+ * @param voice_channel_resources - Workbuffer for the voice channel resources.
+ * @param cpu_states - Workbuffer for the host-side voice states.
+ * @param dsp_states - Workbuffer for the AudioRenderer-side voice states.
+ * @param voice_count - The number of voices in each workbuffer.
+ */
+ void Initialize(std::span<VoiceInfo*> sorted_voice_infos, std::span<VoiceInfo> voice_infos,
+ std::span<VoiceChannelResource> voice_channel_resources,
+ std::span<VoiceState> cpu_states, std::span<VoiceState> dsp_states,
+ u32 voice_count);
+
+ /**
+ * Get a sorted voice with the given index.
+ *
+ * @param index - The sorted voice index to get.
+ * @return The sorted voice.
+ */
+ VoiceInfo* GetSortedInfo(u32 index);
+
+ /**
+ * Get a voice with the given index.
+ *
+ * @param index - The voice index to get.
+ * @return The voice.
+ */
+ VoiceInfo& GetInfo(u32 index);
+
+ /**
+ * Get a host voice state with the given index.
+ *
+ * @param index - The host voice state index to get.
+ * @return The voice state.
+ */
+ VoiceState& GetState(u32 index);
+
+ /**
+ * Get the maximum number of voices.
+ * Not all voices in the buffers may be in use, see GetActiveCount.
+ *
+ * @return The maximum number of voices.
+ */
+ u32 GetCount() const;
+
+ /**
+ * Get the number of active voices.
+ * Can be less than or equal to the maximum number of voices.
+ *
+ * @return The number of active voices.
+ */
+ u32 GetActiveCount() const;
+
+ /**
+ * Set the number of active voices.
+ * Can be less than or equal to the maximum number of voices.
+ *
+ * @param active_count - The new number of active voices.
+ */
+ void SetActiveCount(u32 active_count);
+
+ /**
+ * Sort all voices. Results are available via GetSortedInfo.
+ * Voices are sorted descendingly, according to priority, and then sort order.
+ */
+ void SortInfo();
+
+ /**
+ * Update all voice states, copying AudioRenderer-side states to host-side states.
+ */
+ void UpdateStateByDspShared();
+
+private:
+ /// Sorted voices
+ std::span<VoiceInfo*> sorted_voice_info{};
+ /// Voices
+ std::span<VoiceInfo> voices{};
+ /// Channel resources
+ std::span<VoiceChannelResource> channel_resources{};
+ /// Host-side voice states
+ std::span<VoiceState> cpu_states{};
+ /// AudioRenderer-side voice states
+ std::span<VoiceState> dsp_states{};
+ /// Maximum number of voices
+ u32 voice_count{};
+ /// Number of active voices
+ u32 active_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_info.cpp b/src/audio_core/renderer/voice/voice_info.cpp
new file mode 100644
index 000000000..1849eeb57
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_info.cpp
@@ -0,0 +1,408 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/memory/pool_mapper.h"
+#include "audio_core/renderer/voice/voice_context.h"
+#include "audio_core/renderer/voice/voice_info.h"
+#include "audio_core/renderer/voice/voice_state.h"
+
+namespace AudioCore::AudioRenderer {
+
+VoiceInfo::VoiceInfo() {
+ Initialize();
+}
+
+void VoiceInfo::Initialize() {
+ in_use = false;
+ is_new = false;
+ id = 0;
+ node_id = 0;
+ current_play_state = ServerPlayState::Stopped;
+ src_quality = SrcQuality::Medium;
+ priority = LowestVoicePriority;
+ sample_format = SampleFormat::Invalid;
+ sample_rate = 0;
+ channel_count = 0;
+ wave_buffer_count = 0;
+ wave_buffer_index = 0;
+ pitch = 0.0f;
+ volume = 0.0f;
+ prev_volume = 0.0f;
+ mix_id = UnusedMixId;
+ splitter_id = UnusedSplitterId;
+ biquads = {};
+ biquad_initialized = {};
+ voice_dropped = false;
+ data_unmapped = false;
+ buffer_unmapped = false;
+ flush_buffer_count = 0;
+
+ data_address.Setup(0, 0);
+ for (auto& wavebuffer : wavebuffers) {
+ wavebuffer.Initialize();
+ }
+}
+
+bool VoiceInfo::ShouldUpdateParameters(const InParameter& params) const {
+ return data_address.GetCpuAddr() != params.src_data_address ||
+ data_address.GetSize() != params.src_data_size || data_unmapped;
+}
+
+void VoiceInfo::UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
+ const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
+ in_use = params.in_use;
+ id = params.id;
+ node_id = params.node_id;
+ UpdatePlayState(params.play_state);
+ UpdateSrcQuality(params.src_quality);
+ priority = params.priority;
+ sort_order = params.sort_order;
+ sample_rate = params.sample_rate;
+ sample_format = params.sample_format;
+ channel_count = static_cast<s8>(params.channel_count);
+ pitch = params.pitch;
+ volume = params.volume;
+ biquads = params.biquads;
+ wave_buffer_count = params.wave_buffer_count;
+ wave_buffer_index = params.wave_buffer_index;
+
+ if (behavior.IsFlushVoiceWaveBuffersSupported()) {
+ flush_buffer_count += params.flush_buffer_count;
+ }
+
+ mix_id = params.mix_id;
+
+ if (behavior.IsSplitterSupported()) {
+ splitter_id = params.splitter_id;
+ } else {
+ splitter_id = UnusedSplitterId;
+ }
+
+ channel_resource_ids = params.channel_resource_ids;
+
+ flags &= u16(~0b11);
+ if (behavior.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
+ flags |= u16(params.flags.IsVoicePlayedSampleCountResetAtLoopPointSupported);
+ }
+
+ if (behavior.IsVoicePitchAndSrcSkippedSupported()) {
+ flags |= u16(params.flags.IsVoicePitchAndSrcSkippedSupported);
+ }
+
+ if (params.clear_voice_drop) {
+ voice_dropped = false;
+ }
+
+ if (ShouldUpdateParameters(params)) {
+ data_unmapped = !pool_mapper.TryAttachBuffer(error_info, data_address,
+ params.src_data_address, params.src_data_size);
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void VoiceInfo::UpdatePlayState(const PlayState state) {
+ last_play_state = current_play_state;
+
+ switch (state) {
+ case PlayState::Started:
+ current_play_state = ServerPlayState::Started;
+ break;
+ case PlayState::Stopped:
+ if (current_play_state != ServerPlayState::Stopped) {
+ current_play_state = ServerPlayState::RequestStop;
+ }
+ break;
+ case PlayState::Paused:
+ current_play_state = ServerPlayState::Paused;
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid input play state {}", static_cast<u32>(state));
+ break;
+ }
+}
+
+void VoiceInfo::UpdateSrcQuality(const SrcQuality quality) {
+ switch (quality) {
+ case SrcQuality::Medium:
+ src_quality = quality;
+ break;
+ case SrcQuality::High:
+ src_quality = quality;
+ break;
+ case SrcQuality::Low:
+ src_quality = quality;
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid input src quality {}", static_cast<u32>(quality));
+ break;
+ }
+}
+
+void VoiceInfo::UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
+ [[maybe_unused]] u32 error_count, const InParameter& params,
+ std::span<VoiceState*> voice_states,
+ const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
+ if (params.is_new) {
+ for (size_t i = 0; i < wavebuffers.size(); i++) {
+ wavebuffers[i].Initialize();
+ }
+
+ for (s8 channel = 0; channel < static_cast<s8>(params.channel_count); channel++) {
+ voice_states[channel]->wave_buffer_valid.fill(false);
+ }
+ }
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ UpdateWaveBuffer(error_infos[i], wavebuffers[i], params.wave_buffer_internal[i],
+ params.sample_format, voice_states[0]->wave_buffer_valid[i], pool_mapper,
+ behavior);
+ }
+}
+
+void VoiceInfo::UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info,
+ WaveBuffer& wave_buffer,
+ const WaveBufferInternal& wave_buffer_internal,
+ const SampleFormat sample_format_, const bool valid,
+ const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
+ if (!valid && wave_buffer.sent_to_DSP && wave_buffer.buffer_address.GetCpuAddr() != 0) {
+ pool_mapper.ForceUnmapPointer(wave_buffer.buffer_address);
+ wave_buffer.buffer_address.Setup(0, 0);
+ }
+
+ if (!ShouldUpdateWaveBuffer(wave_buffer_internal)) {
+ return;
+ }
+
+ switch (sample_format_) {
+ case SampleFormat::PcmInt16: {
+ constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmInt16)};
+ if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size ||
+ wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) {
+ LOG_ERROR(Service_Audio, "Invalid PCM16 start/end wavebuffer sizes!");
+ error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
+ error_info[0].address = wave_buffer_internal.address;
+ return;
+ }
+ } break;
+
+ case SampleFormat::PcmFloat: {
+ constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmFloat)};
+ if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size ||
+ wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) {
+ LOG_ERROR(Service_Audio, "Invalid PCMFloat start/end wavebuffer sizes!");
+ error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
+ error_info[0].address = wave_buffer_internal.address;
+ return;
+ }
+ } break;
+
+ case SampleFormat::Adpcm: {
+ const auto start_frame{wave_buffer_internal.start_offset / 14};
+ auto start_extra{wave_buffer_internal.start_offset % 14 == 0
+ ? 0
+ : (wave_buffer_internal.start_offset % 14) / 2 + 1 +
+ ((wave_buffer_internal.start_offset % 14) % 2)};
+ const auto start{start_frame * 8 + start_extra};
+
+ const auto end_frame{wave_buffer_internal.end_offset / 14};
+ const auto end_extra{wave_buffer_internal.end_offset % 14 == 0
+ ? 0
+ : (wave_buffer_internal.end_offset % 14) / 2 + 1 +
+ ((wave_buffer_internal.end_offset % 14) % 2)};
+ const auto end{end_frame * 8 + end_extra};
+
+ if (start > static_cast<s64>(wave_buffer_internal.size) ||
+ end > static_cast<s64>(wave_buffer_internal.size)) {
+ LOG_ERROR(Service_Audio, "Invalid ADPCM start/end wavebuffer sizes!");
+ error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
+ error_info[0].address = wave_buffer_internal.address;
+ return;
+ }
+ } break;
+
+ default:
+ break;
+ }
+
+ if (wave_buffer_internal.start_offset < 0 || wave_buffer_internal.end_offset < 0) {
+ LOG_ERROR(Service_Audio, "Invalid input start/end wavebuffer sizes!");
+ error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
+ error_info[0].address = wave_buffer_internal.address;
+ return;
+ }
+
+ wave_buffer.start_offset = wave_buffer_internal.start_offset;
+ wave_buffer.end_offset = wave_buffer_internal.end_offset;
+ wave_buffer.loop = wave_buffer_internal.loop;
+ wave_buffer.stream_ended = wave_buffer_internal.stream_ended;
+ wave_buffer.sent_to_DSP = false;
+ wave_buffer.loop_start_offset = wave_buffer_internal.loop_start;
+ wave_buffer.loop_end_offset = wave_buffer_internal.loop_end;
+ wave_buffer.loop_count = wave_buffer_internal.loop_count;
+
+ buffer_unmapped =
+ !pool_mapper.TryAttachBuffer(error_info[0], wave_buffer.buffer_address,
+ wave_buffer_internal.address, wave_buffer_internal.size);
+
+ if (sample_format_ == SampleFormat::Adpcm && behavior.IsAdpcmLoopContextBugFixed() &&
+ wave_buffer_internal.context_address != 0) {
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(error_info[1], wave_buffer.context_address,
+ wave_buffer_internal.context_address,
+ wave_buffer_internal.context_size) ||
+ data_unmapped;
+ } else {
+ wave_buffer.context_address.Setup(0, 0);
+ }
+}
+
+bool VoiceInfo::ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const {
+ return !wave_buffer_internal.sent_to_DSP || buffer_unmapped;
+}
+
+void VoiceInfo::WriteOutStatus(OutStatus& out_status, const InParameter& params,
+ std::span<VoiceState*> voice_states) {
+ if (params.is_new) {
+ is_new = true;
+ }
+
+ if (params.is_new || is_new) {
+ out_status.played_sample_count = 0;
+ out_status.wave_buffers_consumed = 0;
+ out_status.voice_dropped = false;
+ } else {
+ out_status.played_sample_count = voice_states[0]->played_sample_count;
+ out_status.wave_buffers_consumed = voice_states[0]->wave_buffers_consumed;
+ out_status.voice_dropped = voice_dropped;
+ }
+}
+
+bool VoiceInfo::ShouldSkip() const {
+ return !in_use || wave_buffer_count == 0 || data_unmapped || buffer_unmapped || voice_dropped;
+}
+
+bool VoiceInfo::HasAnyConnection() const {
+ return mix_id != UnusedMixId || splitter_id != UnusedSplitterId;
+}
+
+void VoiceInfo::FlushWaveBuffers(const u32 flush_count, std::span<VoiceState*> voice_states,
+ const s8 channel_count_) {
+ auto wave_index{wave_buffer_index};
+
+ for (size_t i = 0; i < flush_count; i++) {
+ wavebuffers[wave_index].sent_to_DSP = true;
+
+ for (s8 j = 0; j < channel_count_; j++) {
+ auto voice_state{voice_states[j]};
+ if (voice_state->wave_buffer_index == wave_index) {
+ voice_state->wave_buffer_index =
+ (voice_state->wave_buffer_index + 1) % MaxWaveBuffers;
+ voice_state->wave_buffers_consumed++;
+ }
+ voice_state->wave_buffer_valid[wave_index] = false;
+ }
+
+ wave_index = (wave_index + 1) % MaxWaveBuffers;
+ }
+}
+
+bool VoiceInfo::UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states) {
+ if (flush_buffer_count > 0) {
+ FlushWaveBuffers(flush_buffer_count, voice_states, channel_count);
+ flush_buffer_count = 0;
+ }
+
+ switch (current_play_state) {
+ case ServerPlayState::Started:
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ if (!wavebuffers[i].sent_to_DSP) {
+ for (s8 channel = 0; channel < channel_count; channel++) {
+ voice_states[channel]->wave_buffer_valid[i] = true;
+ }
+ wavebuffers[i].sent_to_DSP = true;
+ }
+ }
+
+ was_playing = false;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ if (voice_states[0]->wave_buffer_valid[i]) {
+ return true;
+ }
+ }
+ break;
+
+ case ServerPlayState::Stopped:
+ case ServerPlayState::Paused:
+ for (auto& wavebuffer : wavebuffers) {
+ if (!wavebuffer.sent_to_DSP) {
+ wavebuffer.buffer_address.GetReference(true);
+ wavebuffer.context_address.GetReference(true);
+ }
+ }
+
+ if (sample_format == SampleFormat::Adpcm && data_address.GetCpuAddr() != 0) {
+ data_address.GetReference(true);
+ }
+
+ was_playing = last_play_state == ServerPlayState::Started;
+ break;
+
+ case ServerPlayState::RequestStop:
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ wavebuffers[i].sent_to_DSP = true;
+
+ for (s8 channel = 0; channel < channel_count; channel++) {
+ if (voice_states[channel]->wave_buffer_valid[i]) {
+ voice_states[channel]->wave_buffer_index =
+ (voice_states[channel]->wave_buffer_index + 1) % MaxWaveBuffers;
+ voice_states[channel]->wave_buffers_consumed++;
+ }
+ voice_states[channel]->wave_buffer_valid[i] = false;
+ }
+ }
+
+ for (s8 channel = 0; channel < channel_count; channel++) {
+ voice_states[channel]->offset = 0;
+ voice_states[channel]->played_sample_count = 0;
+ voice_states[channel]->adpcm_context = {};
+ voice_states[channel]->sample_history.fill(0);
+ voice_states[channel]->fraction = 0;
+ }
+
+ current_play_state = ServerPlayState::Stopped;
+ was_playing = last_play_state == ServerPlayState::Started;
+ break;
+ }
+
+ return was_playing;
+}
+
+bool VoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
+ std::array<VoiceState*, MaxChannels> voice_states{};
+
+ if (is_new) {
+ ResetResources(voice_context);
+ prev_volume = volume;
+ is_new = false;
+ }
+
+ for (s8 channel = 0; channel < channel_count; channel++) {
+ voice_states[channel] = &voice_context.GetDspSharedState(channel_resource_ids[channel]);
+ }
+
+ return UpdateParametersForCommandGeneration(voice_states);
+}
+
+void VoiceInfo::ResetResources(VoiceContext& voice_context) const {
+ for (s8 channel = 0; channel < channel_count; channel++) {
+ auto& state{voice_context.GetDspSharedState(channel_resource_ids[channel])};
+ state = {};
+
+ auto& channel_resource{voice_context.GetChannelResource(channel_resource_ids[channel])};
+ channel_resource.prev_mix_volumes = channel_resource.mix_volumes;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h
new file mode 100644
index 000000000..930180895
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_info.h
@@ -0,0 +1,380 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <bitset>
+
+#include "audio_core/common/common.h"
+#include "audio_core/common/wave_buffer.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/memory/address_info.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class PoolMapper;
+class VoiceContext;
+struct VoiceState;
+
+/**
+ * Represents one voice. Voices are essentially noises, and they can be further mixed and have
+ * effects applied to them, but voices are the basis of all sounds.
+ */
+class VoiceInfo {
+public:
+ enum class ServerPlayState {
+ Started,
+ Stopped,
+ RequestStop,
+ Paused,
+ };
+
+ struct Flags {
+ u8 IsVoicePlayedSampleCountResetAtLoopPointSupported : 1;
+ u8 IsVoicePitchAndSrcSkippedSupported : 1;
+ };
+
+ /**
+ * A wavebuffer contains information on the data source buffers.
+ */
+ struct WaveBuffer {
+ void Copy(WaveBufferVersion1& other) {
+ other.buffer = buffer_address.GetReference(true);
+ other.buffer_size = buffer_address.GetSize();
+ other.start_offset = start_offset;
+ other.end_offset = end_offset;
+ other.loop = loop;
+ other.stream_ended = stream_ended;
+
+ if (context_address.GetCpuAddr()) {
+ other.context = context_address.GetReference(true);
+ other.context_size = context_address.GetSize();
+ } else {
+ other.context = CpuAddr(0);
+ other.context_size = 0;
+ }
+ }
+
+ void Copy(WaveBufferVersion2& other) {
+ other.buffer = buffer_address.GetReference(true);
+ other.buffer_size = buffer_address.GetSize();
+ other.start_offset = start_offset;
+ other.end_offset = end_offset;
+ other.loop_start_offset = loop_start_offset;
+ other.loop_end_offset = loop_end_offset;
+ other.loop = loop;
+ other.loop_count = loop_count;
+ other.stream_ended = stream_ended;
+
+ if (context_address.GetCpuAddr()) {
+ other.context = context_address.GetReference(true);
+ other.context_size = context_address.GetSize();
+ } else {
+ other.context = CpuAddr(0);
+ other.context_size = 0;
+ }
+ }
+
+ void Initialize() {
+ buffer_address.Setup(0, 0);
+ context_address.Setup(0, 0);
+ start_offset = 0;
+ end_offset = 0;
+ loop = false;
+ stream_ended = false;
+ sent_to_DSP = true;
+ loop_start_offset = 0;
+ loop_end_offset = 0;
+ loop_count = 0;
+ }
+ /// Game memory address of the wavebuffer data
+ AddressInfo buffer_address{0, 0};
+ /// Context for decoding, used for ADPCM
+ AddressInfo context_address{0, 0};
+ /// Starting offset for the wavebuffer
+ u32 start_offset{};
+ /// Ending offset the wavebuffer
+ u32 end_offset{};
+ /// Should this wavebuffer loop?
+ bool loop{};
+ /// Has this wavebuffer ended?
+ bool stream_ended{};
+ /// Has this wavebuffer been sent to the AudioRenderer?
+ bool sent_to_DSP{true};
+ /// Starting offset when looping, can differ from start_offset
+ u32 loop_start_offset{};
+ /// Ending offset when looping, can differ from end_offset
+ u32 loop_end_offset{};
+ /// Number of times to loop this wavebuffer
+ s32 loop_count{};
+ };
+
+ struct WaveBufferInternal {
+ /* 0x00 */ CpuAddr address;
+ /* 0x08 */ u64 size;
+ /* 0x10 */ s32 start_offset;
+ /* 0x14 */ s32 end_offset;
+ /* 0x18 */ bool loop;
+ /* 0x19 */ bool stream_ended;
+ /* 0x1A */ bool sent_to_DSP;
+ /* 0x1C */ s32 loop_count;
+ /* 0x20 */ CpuAddr context_address;
+ /* 0x28 */ u64 context_size;
+ /* 0x30 */ u32 loop_start;
+ /* 0x34 */ u32 loop_end;
+ };
+ static_assert(sizeof(WaveBufferInternal) == 0x38,
+ "VoiceInfo::WaveBufferInternal has the wrong size!");
+
+ struct BiquadFilterParameter {
+ /* 0x00 */ bool enabled;
+ /* 0x02 */ std::array<s16, 3> b;
+ /* 0x08 */ std::array<s16, 2> a;
+ };
+ static_assert(sizeof(BiquadFilterParameter) == 0xC,
+ "VoiceInfo::BiquadFilterParameter has the wrong size!");
+
+ struct InParameter {
+ /* 0x000 */ u32 id;
+ /* 0x004 */ u32 node_id;
+ /* 0x008 */ bool is_new;
+ /* 0x009 */ bool in_use;
+ /* 0x00A */ PlayState play_state;
+ /* 0x00B */ SampleFormat sample_format;
+ /* 0x00C */ u32 sample_rate;
+ /* 0x010 */ s32 priority;
+ /* 0x014 */ s32 sort_order;
+ /* 0x018 */ u32 channel_count;
+ /* 0x01C */ f32 pitch;
+ /* 0x020 */ f32 volume;
+ /* 0x024 */ std::array<BiquadFilterParameter, MaxBiquadFilters> biquads;
+ /* 0x03C */ u32 wave_buffer_count;
+ /* 0x040 */ u16 wave_buffer_index;
+ /* 0x042 */ char unk042[0x6];
+ /* 0x048 */ CpuAddr src_data_address;
+ /* 0x050 */ u64 src_data_size;
+ /* 0x058 */ u32 mix_id;
+ /* 0x05C */ u32 splitter_id;
+ /* 0x060 */ std::array<WaveBufferInternal, MaxWaveBuffers> wave_buffer_internal;
+ /* 0x140 */ std::array<u32, MaxChannels> channel_resource_ids;
+ /* 0x158 */ bool clear_voice_drop;
+ /* 0x159 */ u8 flush_buffer_count;
+ /* 0x15A */ char unk15A[0x2];
+ /* 0x15C */ Flags flags;
+ /* 0x15D */ char unk15D[0x1];
+ /* 0x15E */ SrcQuality src_quality;
+ /* 0x15F */ char unk15F[0x11];
+ };
+ static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!");
+
+ struct OutStatus {
+ /* 0x00 */ u64 played_sample_count;
+ /* 0x08 */ u32 wave_buffers_consumed;
+ /* 0x0C */ bool voice_dropped;
+ };
+ static_assert(sizeof(OutStatus) == 0x10, "OutStatus::InParameter has the wrong size!");
+
+ VoiceInfo();
+
+ /**
+ * Initialize this voice.
+ */
+ void Initialize();
+
+ /**
+ * Does this voice ned an update?
+ *
+ * @param params - Input parameters to check matching.
+ *
+ * @return True if this voice needs an update, otherwise false.
+ */
+ bool ShouldUpdateParameters(const InParameter& params) const;
+
+ /**
+ * Update the parameters of this voice.
+ *
+ * @param error_info - Output error code.
+ * @param params - Input parameters to update from.
+ * @param pool_mapper - Used to map buffers.
+ * @param behavior - behavior to check supported features.
+ */
+ void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
+ const PoolMapper& pool_mapper, const BehaviorInfo& behavior);
+
+ /**
+ * Update the current play state.
+ *
+ * @param state - New play state for this voice.
+ */
+ void UpdatePlayState(PlayState state);
+
+ /**
+ * Update the current sample rate conversion quality.
+ *
+ * @param quality - New quality.
+ */
+ void UpdateSrcQuality(SrcQuality quality);
+
+ /**
+ * Update all wavebuffers.
+ *
+ * @param error_infos - Output 2D array of errors, 2 per wavebuffer.
+ * @param error_count - Number of errors provided. Unused.
+ * @param params - Input parameters to be used for the update.
+ * @param voice_states - The voice states for each channel in this voice to be updated.
+ * @param pool_mapper - Used to map the wavebuffers.
+ * @param behavior - Used to check for supported features.
+ */
+ void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
+ u32 error_count, const InParameter& params,
+ std::span<VoiceState*> voice_states, const PoolMapper& pool_mapper,
+ const BehaviorInfo& behavior);
+
+ /**
+ * Update a wavebuffer.
+ *
+ * @param error_info - Output array of errors.
+ * @param wave_buffer - The wavebuffer to be updated.
+ * @param wave_buffer_internal - Input parametters to be used for the update.
+ * @param sample_format - Sample format of the wavebuffer.
+ * @param valid - Is this wavebuffer valid?
+ * @param pool_mapper - Used to map the wavebuffers.
+ * @param behavior - Used to check for supported features.
+ */
+ void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer,
+ const WaveBufferInternal& wave_buffer_internal,
+ SampleFormat sample_format, bool valid, const PoolMapper& pool_mapper,
+ const BehaviorInfo& behavior);
+
+ /**
+ * Check if the input wavebuffer needs an update.
+ *
+ * @param wave_buffer_internal - Input wavebuffer parameters to check.
+ * @return True if the given wavebuffer needs an update, otherwise false.
+ */
+ bool ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const;
+
+ /**
+ * Write the number of played samples, number of consumed wavebuffers and if this voice was
+ * dropped, to the given out_status.
+ *
+ * @param out_status - Output status to be written to.
+ * @param in_params - Input parameters to check if the wavebuffer is new.
+ * @param voice_states - Current host voice states for this voice, source of the output.
+ */
+ void WriteOutStatus(OutStatus& out_status, const InParameter& in_params,
+ std::span<VoiceState*> voice_states);
+
+ /**
+ * Check if this voice should be skipped for command generation.
+ * Checks various things such as usage state, whether data is mapped etc.
+ *
+ * @return True if this voice should not be generated, otherwise false.
+ */
+ bool ShouldSkip() const;
+
+ /**
+ * Check if this voice has any mixing connections.
+ *
+ * @return True if this voice participates in mixing, otherwise false.
+ */
+ bool HasAnyConnection() const;
+
+ /**
+ * Flush flush_count wavebuffers, marking them as consumed.
+ *
+ * @param flush_count - Number of wavebuffers to flush.
+ * @param voice_states - Voice states for these wavebuffers.
+ * @param channel_count - Number of active channels.
+ */
+ void FlushWaveBuffers(u32 flush_count, std::span<VoiceState*> voice_states, s8 channel_count);
+
+ /**
+ * Update this voice's parameters on command generation,
+ * updating voice states and flushing if needed.
+ *
+ * @param voice_states - Voice states for these wavebuffers.
+ * @return True if this voice should be generated, otherwise false.
+ */
+ bool UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states);
+
+ /**
+ * Update this voice on command generation.
+ *
+ * @param voice_context - Voice context for these wavebuffers.
+ *
+ * @return True if this voice should be generated, otherwise false.
+ */
+ bool UpdateForCommandGeneration(VoiceContext& voice_context);
+
+ /**
+ * Reset the AudioRenderer-side voice states, and the channel resources for this voice.
+ *
+ * @param voice_context - Context from which to get the resources.
+ */
+ void ResetResources(VoiceContext& voice_context) const;
+
+ /// Is this voice in use?
+ bool in_use{};
+ /// Is this voice new?
+ bool is_new{};
+ /// Was this voice last playing? Used for depopping
+ bool was_playing{};
+ /// Sample format of the wavebuffers in this voice
+ SampleFormat sample_format{};
+ /// Sample rate of the wavebuffers in this voice
+ u32 sample_rate{};
+ /// Number of channels in this voice
+ s8 channel_count{};
+ /// Id of this voice
+ u32 id{};
+ /// Node id of this voice
+ u32 node_id{};
+ /// Mix id this voice is mixed to
+ u32 mix_id{};
+ /// Play state of this voice
+ ServerPlayState current_play_state{ServerPlayState::Stopped};
+ /// Last play state of this voice
+ ServerPlayState last_play_state{ServerPlayState::Started};
+ /// Priority of this voice, lower is higher
+ s32 priority{};
+ /// Sort order of this voice, used when same priority
+ s32 sort_order{};
+ /// Pitch of this voice (for sample rate conversion)
+ f32 pitch{};
+ /// Current volume of this voice
+ f32 volume{};
+ /// Previous volume of this voice
+ f32 prev_volume{};
+ /// Biquad filters for generating filter commands on this voice
+ std::array<BiquadFilterParameter, MaxBiquadFilters> biquads{};
+ /// Number of active wavebuffers
+ u32 wave_buffer_count{};
+ /// Current playing wavebuffer index
+ u16 wave_buffer_index{};
+ /// Flags controlling decode behavior
+ u16 flags{};
+ /// Game memory for ADPCM coefficients
+ AddressInfo data_address{0, 0};
+ /// Wavebuffers
+ std::array<WaveBuffer, MaxWaveBuffers> wavebuffers{};
+ /// Channel resources for this voice
+ std::array<u32, MaxChannels> channel_resource_ids{};
+ /// Splitter id this voice is connected with
+ s32 splitter_id{UnusedSplitterId};
+ /// Sample rate conversion quality
+ SrcQuality src_quality{SrcQuality::Medium};
+ /// Was this voice dropped due to limited time?
+ bool voice_dropped{};
+ /// Is this voice's coefficient (data_address) unmapped?
+ bool data_unmapped{};
+ /// Is this voice's buffers (wavebuffer data and ADPCM context) unmapped?
+ bool buffer_unmapped{};
+ /// Initialisation state of the biquads
+ std::array<bool, MaxBiquadFilters> biquad_initialized{};
+ /// Number of wavebuffers to flush
+ u8 flush_buffer_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_state.h b/src/audio_core/renderer/voice/voice_state.h
new file mode 100644
index 000000000..d5497e2fb
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_state.h
@@ -0,0 +1,70 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer,
+ * host-side is updated on the next iteration.
+ */
+struct VoiceState {
+ /**
+ * State of the voice's biquad filter.
+ */
+ struct BiquadFilterState {
+ Common::FixedPoint<50, 14> s0;
+ Common::FixedPoint<50, 14> s1;
+ Common::FixedPoint<50, 14> s2;
+ Common::FixedPoint<50, 14> s3;
+ };
+
+ /**
+ * Context for ADPCM decoding.
+ */
+ struct AdpcmContext {
+ u16 header;
+ s16 yn0;
+ s16 yn1;
+ };
+
+ /// Number of samples played
+ u64 played_sample_count;
+ /// Current offset from the starting offset
+ u32 offset;
+ /// Currently active wavebuffer index
+ u32 wave_buffer_index;
+ /// Array of which wavebuffers are currently valid
+
+ std::array<bool, MaxWaveBuffers> wave_buffer_valid;
+ /// Number of wavebuffers consumed, given back to the game
+ u32 wave_buffers_consumed;
+ /// History of samples, used for rate conversion
+
+ std::array<s16, MaxWaveBuffers * 2> sample_history;
+ /// Current read fraction, used for resampling
+ Common::FixedPoint<49, 15> fraction;
+ /// Current adpcm context
+ AdpcmContext adpcm_context;
+ /// Current biquad states, used when filtering
+
+ std::array<std::array<BiquadFilterState, MaxBiquadFilters>, MaxBiquadFilters> biquad_states;
+ /// Previous samples
+ std::array<s32, MaxMixBuffers> previous_samples;
+ /// Unused
+ u32 external_context_size;
+ /// Unused
+ bool external_context_enabled;
+ /// Was this voice dropped?
+ bool voice_dropped;
+ /// Number of times the wavebuffer has looped
+ s32 loop_count;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp
deleted file mode 100644
index 62d3716a6..000000000
--- a/src/audio_core/sdl2_sink.cpp
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <atomic>
-#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"
-
-// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
-#ifdef __clang__
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
-#endif
-#include <SDL.h>
-#ifdef __clang__
-#pragma clang diagnostic pop
-#endif
-
-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} {
-
- SDL_AudioSpec spec;
- spec.freq = sample_rate;
- spec.channels = static_cast<u8>(num_channels);
- spec.format = AUDIO_S16SYS;
- spec.samples = 4096;
- spec.callback = nullptr;
-
- SDL_AudioSpec obtained;
- if (output_device.empty()) {
- dev = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained, 0);
- } else {
- dev = SDL_OpenAudioDevice(output_device.c_str(), 0, &spec, &obtained, 0);
- }
-
- if (dev == 0) {
- LOG_CRITICAL(Audio_Sink, "Error opening sdl audio device: {}", SDL_GetError());
- return;
- }
-
- SDL_PauseAudioDevice(dev, 0);
- }
-
- ~SDLSinkStream() override {
- if (dev == 0) {
- return;
- }
-
- SDL_CloseAudioDevice(dev);
- }
-
- void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override {
- if (source_num_channels > num_channels) {
- // Downsample 6 channels to 2
- ASSERT_MSG(source_num_channels == 6, "Channel count must be 6");
-
- std::vector<s16> buf;
- buf.reserve(samples.size() * num_channels / source_num_channels);
- for (std::size_t i = 0; i < samples.size(); i += source_num_channels) {
- // Downmixing implementation taken from the ATSC standard
- const s16 left{samples[i + 0]};
- const s16 right{samples[i + 1]};
- const s16 center{samples[i + 2]};
- const s16 surround_left{samples[i + 4]};
- const s16 surround_right{samples[i + 5]};
- // Not used in the ATSC reference implementation
- [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]};
-
- constexpr s32 clev{707}; // center mixing level coefficient
- constexpr s32 slev{707}; // surround mixing level coefficient
-
- buf.push_back(static_cast<s16>(left + (clev * center / 1000) +
- (slev * surround_left / 1000)));
- buf.push_back(static_cast<s16>(right + (clev * center / 1000) +
- (slev * surround_right / 1000)));
- }
- int ret = SDL_QueueAudio(dev, static_cast<const void*>(buf.data()),
- static_cast<u32>(buf.size() * sizeof(s16)));
- if (ret < 0)
- LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError());
- return;
- }
-
- int ret = SDL_QueueAudio(dev, static_cast<const void*>(samples.data()),
- static_cast<u32>(samples.size() * sizeof(s16)));
- if (ret < 0)
- LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError());
- }
-
- std::size_t SamplesInQueue(u32 channel_count) const override {
- if (dev == 0)
- return 0;
-
- return SDL_GetQueuedAudioSize(dev) / (channel_count * sizeof(s16));
- }
-
- void Flush() override {
- should_flush = true;
- }
-
- u32 GetNumChannels() const {
- return num_channels;
- }
-
-private:
- SDL_AudioDeviceID dev = 0;
- u32 num_channels{};
- std::atomic<bool> should_flush{};
- TimeStretcher time_stretch;
-};
-
-SDLSink::SDLSink(std::string_view target_device_name) {
- if (!SDL_WasInit(SDL_INIT_AUDIO)) {
- if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
- LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
- return;
- }
- }
-
- if (target_device_name != auto_device_name && !target_device_name.empty()) {
- output_device = target_device_name;
- } else {
- output_device.clear();
- }
-}
-
-SDLSink::~SDLSink() = default;
-
-SinkStream& SDLSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, const std::string&) {
- sink_streams.push_back(
- std::make_unique<SDLSinkStream>(sample_rate, num_channels, output_device));
- return *sink_streams.back();
-}
-
-std::vector<std::string> ListSDLSinkDevices() {
- std::vector<std::string> device_list;
-
- if (!SDL_WasInit(SDL_INIT_AUDIO)) {
- if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
- LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
- return {};
- }
- }
-
- const int device_count = SDL_GetNumAudioDevices(0);
- for (int i = 0; i < device_count; ++i) {
- device_list.emplace_back(SDL_GetAudioDeviceName(i, 0));
- }
-
- return device_list;
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h
deleted file mode 100644
index 8ec1526d8..000000000
--- a/src/audio_core/sdl2_sink.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <string>
-#include <vector>
-
-#include "audio_core/sink.h"
-
-namespace AudioCore {
-
-class SDLSink final : public Sink {
-public:
- explicit SDLSink(std::string_view device_id);
- ~SDLSink() override;
-
- SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,
- const std::string& name) override;
-
-private:
- std::string output_device;
- std::vector<SinkStreamPtr> sink_streams;
-};
-
-std::vector<std::string> ListSDLSinkDevices();
-
-} // namespace AudioCore
diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h
deleted file mode 100644
index 95c7b2b6e..000000000
--- a/src/audio_core/sink.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <string>
-
-#include "audio_core/sink_stream.h"
-#include "common/common_types.h"
-
-namespace AudioCore {
-
-constexpr char auto_device_name[] = "auto";
-
-/**
- * This class is an interface for an audio sink. An audio sink accepts samples in stereo signed
- * PCM16 format to be output. Sinks *do not* handle resampling and expect the correct sample rate.
- * They are dumb outputs.
- */
-class Sink {
-public:
- virtual ~Sink() = default;
- virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,
- const std::string& name) = 0;
-};
-
-using SinkPtr = std::unique_ptr<Sink>;
-
-} // namespace AudioCore
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp
new file mode 100644
index 000000000..36b115ad6
--- /dev/null
+++ b/src/audio_core/sink/cubeb_sink.cpp
@@ -0,0 +1,329 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <span>
+#include <vector>
+
+#include "audio_core/common/common.h"
+#include "audio_core/sink/cubeb_sink.h"
+#include "audio_core/sink/sink_stream.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+
+#ifdef _WIN32
+#include <objbase.h>
+#undef CreateEvent
+#endif
+
+namespace AudioCore::Sink {
+/**
+ * Cubeb sink stream, responsible for sinking samples to hardware.
+ */
+class CubebSinkStream final : public SinkStream {
+public:
+ /**
+ * Create a new sink stream.
+ *
+ * @param ctx_ - Cubeb context to create this stream with.
+ * @param device_channels_ - Number of channels supported by the hardware.
+ * @param system_channels_ - Number of channels the audio systems expect.
+ * @param output_device - Cubeb output device id.
+ * @param input_device - Cubeb input device id.
+ * @param name_ - Name of this stream.
+ * @param type_ - Type of this stream.
+ * @param system_ - Core system.
+ * @param event - Event used only for audio renderer, signalled on buffer consume.
+ */
+ CubebSinkStream(cubeb* ctx_, u32 device_channels_, u32 system_channels_,
+ cubeb_devid output_device, cubeb_devid input_device, const std::string& name_,
+ StreamType type_, Core::System& system_)
+ : SinkStream(system_, type_), ctx{ctx_} {
+#ifdef _WIN32
+ CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+#endif
+ name = name_;
+ device_channels = device_channels_;
+ system_channels = system_channels_;
+
+ cubeb_stream_params params{};
+ params.rate = TargetSampleRate;
+ params.channels = device_channels;
+ params.format = CUBEB_SAMPLE_S16LE;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+ switch (params.channels) {
+ case 1:
+ params.layout = CUBEB_LAYOUT_MONO;
+ break;
+ case 2:
+ params.layout = CUBEB_LAYOUT_STEREO;
+ break;
+ case 6:
+ params.layout = CUBEB_LAYOUT_3F2_LFE;
+ break;
+ }
+
+ u32 minimum_latency{0};
+ const auto latency_error = cubeb_get_min_latency(ctx, &params, &minimum_latency);
+ if (latency_error != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
+ minimum_latency = 256U;
+ }
+
+ minimum_latency = std::max(minimum_latency, 256u);
+
+ LOG_INFO(Service_Audio,
+ "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) "
+ "latency {}",
+ name, type, params.rate, params.channels, system_channels, minimum_latency);
+
+ auto init_error{0};
+ if (type == StreamType::In) {
+ init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device,
+ &params, output_device, nullptr, minimum_latency,
+ &CubebSinkStream::DataCallback,
+ &CubebSinkStream::StateCallback, this);
+ } else {
+ init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device,
+ nullptr, output_device, &params, minimum_latency,
+ &CubebSinkStream::DataCallback,
+ &CubebSinkStream::StateCallback, this);
+ }
+
+ if (init_error != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream, error: {}", init_error);
+ return;
+ }
+ }
+
+ /**
+ * Destroy the sink stream.
+ */
+ ~CubebSinkStream() override {
+ LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name);
+
+ Unstall();
+
+ if (!ctx) {
+ return;
+ }
+
+ Finalize();
+
+#ifdef _WIN32
+ CoUninitialize();
+#endif
+ }
+
+ /**
+ * Finalize the sink stream.
+ */
+ void Finalize() override {
+ Stop();
+ cubeb_stream_destroy(stream_backend);
+ }
+
+ /**
+ * Start the sink stream.
+ *
+ * @param resume - Set to true if this is resuming the stream a previously-active stream.
+ * Default false.
+ */
+ void Start(bool resume = false) override {
+ if (!ctx || !paused) {
+ return;
+ }
+
+ paused = false;
+ if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
+ }
+ }
+
+ /**
+ * Stop the sink stream.
+ */
+ void Stop() override {
+ Unstall();
+
+ if (!ctx || paused) {
+ return;
+ }
+
+ paused = true;
+ if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
+ }
+ }
+
+private:
+ /**
+ * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will
+ * provide samples to be copied (audio in).
+ *
+ * @param stream - Cubeb-specific data about the stream.
+ * @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
+ * @param in_buff - Input buffer to be used if the stream is an input type.
+ * @param out_buff - Output buffer to be used if the stream is an output type.
+ * @param num_frames_ - Number of frames of audio in the buffers. Note: Not number of samples.
+ */
+ static long DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data,
+ [[maybe_unused]] const void* in_buff, void* out_buff,
+ long num_frames_) {
+ auto* impl = static_cast<CubebSinkStream*>(user_data);
+ if (!impl) {
+ return -1;
+ }
+
+ const std::size_t num_channels = impl->GetDeviceChannels();
+ const std::size_t frame_size = num_channels;
+ const std::size_t num_frames{static_cast<size_t>(num_frames_)};
+
+ if (impl->type == StreamType::In) {
+ std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff),
+ num_frames * frame_size};
+ impl->ProcessAudioIn(input_buffer, num_frames);
+ } else {
+ std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size};
+ impl->ProcessAudioOutAndRender(output_buffer, num_frames);
+ }
+
+ return num_frames_;
+ }
+
+ /**
+ * Cubeb callback for if a device state changes. Unused currently.
+ *
+ * @param stream - Cubeb-specific data about the stream.
+ * @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
+ * @param state - New state of the device.
+ */
+ static void StateCallback(cubeb_stream*, void*, cubeb_state) {}
+
+ /// Main Cubeb context
+ cubeb* ctx{};
+ /// Cubeb stream backend
+ cubeb_stream* stream_backend{};
+};
+
+CubebSink::CubebSink(std::string_view target_device_name) {
+ // Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
+#ifdef _WIN32
+ com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+#endif
+
+ if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
+ return;
+ }
+
+ if (target_device_name != auto_device_name && !target_device_name.empty()) {
+ cubeb_device_collection collection;
+ if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
+ LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
+ } else {
+ const auto collection_end{collection.device + collection.count};
+ const auto device{
+ std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
+ return info.friendly_name != nullptr &&
+ target_device_name == std::string(info.friendly_name);
+ })};
+ if (device != collection_end) {
+ output_device = device->devid;
+ }
+ cubeb_device_collection_destroy(ctx, &collection);
+ }
+ }
+
+ cubeb_get_max_channel_count(ctx, &device_channels);
+ device_channels = device_channels >= 6U ? 6U : 2U;
+}
+
+CubebSink::~CubebSink() {
+ if (!ctx) {
+ return;
+ }
+
+ for (auto& sink_stream : sink_streams) {
+ sink_stream.reset();
+ }
+
+ cubeb_destroy(ctx);
+
+#ifdef _WIN32
+ if (SUCCEEDED(com_init_result)) {
+ CoUninitialize();
+ }
+#endif
+}
+
+SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels,
+ const std::string& name, StreamType type) {
+ SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
+ ctx, device_channels, system_channels, output_device, input_device, name, type, system));
+
+ return stream.get();
+}
+
+void CubebSink::CloseStream(SinkStream* stream) {
+ for (size_t i = 0; i < sink_streams.size(); i++) {
+ if (sink_streams[i].get() == stream) {
+ sink_streams[i].reset();
+ sink_streams.erase(sink_streams.begin() + i);
+ break;
+ }
+ }
+}
+
+void CubebSink::CloseStreams() {
+ sink_streams.clear();
+}
+
+f32 CubebSink::GetDeviceVolume() const {
+ if (sink_streams.empty()) {
+ return 1.0f;
+ }
+
+ return sink_streams[0]->GetDeviceVolume();
+}
+
+void CubebSink::SetDeviceVolume(f32 volume) {
+ for (auto& stream : sink_streams) {
+ stream->SetDeviceVolume(volume);
+ }
+}
+
+void CubebSink::SetSystemVolume(f32 volume) {
+ for (auto& stream : sink_streams) {
+ stream->SetSystemVolume(volume);
+ }
+}
+
+std::vector<std::string> ListCubebSinkDevices(bool capture) {
+ std::vector<std::string> device_list;
+ cubeb* ctx;
+
+ if (cubeb_init(&ctx, "yuzu Device Enumerator", nullptr) != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
+ return {};
+ }
+
+ auto type{capture ? CUBEB_DEVICE_TYPE_INPUT : CUBEB_DEVICE_TYPE_OUTPUT};
+ cubeb_device_collection collection;
+ if (cubeb_enumerate_devices(ctx, type, &collection) != CUBEB_OK) {
+ LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
+ } else {
+ for (std::size_t i = 0; i < collection.count; i++) {
+ const cubeb_device_info& device = collection.device[i];
+ if (device.friendly_name && device.friendly_name[0] != '\0' &&
+ device.state == CUBEB_DEVICE_STATE_ENABLED) {
+ device_list.emplace_back(device.friendly_name);
+ }
+ }
+ cubeb_device_collection_destroy(ctx, &collection);
+ }
+
+ cubeb_destroy(ctx);
+ return device_list;
+}
+
+} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h
new file mode 100644
index 000000000..4b0cb160d
--- /dev/null
+++ b/src/audio_core/sink/cubeb_sink.h
@@ -0,0 +1,99 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <cubeb/cubeb.h>
+
+#include "audio_core/sink/sink.h"
+
+namespace Core {
+class System;
+}
+
+namespace AudioCore::Sink {
+class SinkStream;
+
+/**
+ * Cubeb backend sink, holds multiple output streams and is responsible for sinking samples to
+ * hardware. Used by Audio Render, Audio In and Audio Out.
+ */
+class CubebSink final : public Sink {
+public:
+ explicit CubebSink(std::string_view device_id);
+ ~CubebSink() override;
+
+ /**
+ * Create a new sink stream.
+ *
+ * @param system - Core system.
+ * @param system_channels - Number of channels the audio system expects.
+ * May differ from the device's channel count.
+ * @param name - Name of this stream.
+ * @param type - Type of this stream, render/in/out.
+ *
+ * @return A pointer to the created SinkStream
+ */
+ SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
+ const std::string& name, StreamType type) override;
+
+ /**
+ * Close a given stream.
+ *
+ * @param stream - The stream to close.
+ */
+ void CloseStream(SinkStream* stream) override;
+
+ /**
+ * Close all streams.
+ */
+ void CloseStreams() override;
+
+ /**
+ * Get the device volume. Set from calls to the IAudioDevice service.
+ *
+ * @return Volume of the device.
+ */
+ f32 GetDeviceVolume() const override;
+
+ /**
+ * Set the device volume. Set from calls to the IAudioDevice service.
+ *
+ * @param volume - New volume of the device.
+ */
+ void SetDeviceVolume(f32 volume) override;
+
+ /**
+ * Set the system volume. Comes from the audio system using this stream.
+ *
+ * @param volume - New volume of the system.
+ */
+ void SetSystemVolume(f32 volume) override;
+
+private:
+ /// Backend Cubeb context
+ cubeb* ctx{};
+ /// Cubeb id of the actual hardware output device
+ cubeb_devid output_device{};
+ /// Cubeb id of the actual hardware input device
+ cubeb_devid input_device{};
+ /// Vector of streams managed by this sink
+ std::vector<SinkStreamPtr> sink_streams{};
+
+#ifdef _WIN32
+ /// Cubeb required COM to be initialized multi-threaded on Windows
+ u32 com_init_result = 0;
+#endif
+};
+
+/**
+ * Get a list of connected devices from Cubeb.
+ *
+ * @param capture - Return input (capture) devices if true, otherwise output devices.
+ */
+std::vector<std::string> ListCubebSinkDevices(bool capture);
+
+} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h
new file mode 100644
index 000000000..1215d3cd2
--- /dev/null
+++ b/src/audio_core/sink/null_sink.h
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "audio_core/sink/sink.h"
+#include "audio_core/sink/sink_stream.h"
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace AudioCore::Sink {
+class NullSinkStreamImpl final : public SinkStream {
+public:
+ explicit NullSinkStreamImpl(Core::System& system_, StreamType type_)
+ : SinkStream{system_, type_} {}
+ ~NullSinkStreamImpl() override {}
+ void AppendBuffer(SinkBuffer&, std::vector<s16>&) override {}
+ std::vector<s16> ReleaseBuffer(u64) override {
+ return {};
+ }
+};
+
+/**
+ * A no-op sink for when no audio out is wanted.
+ */
+class NullSink final : public Sink {
+public:
+ explicit NullSink(std::string_view) {}
+ ~NullSink() override = default;
+
+ SinkStream* AcquireSinkStream(Core::System& system, u32, const std::string&,
+ StreamType type) override {
+ if (null_sink == nullptr) {
+ null_sink = std::make_unique<NullSinkStreamImpl>(system, type);
+ }
+ return null_sink.get();
+ }
+
+ void CloseStream(SinkStream*) override {}
+ void CloseStreams() override {}
+ f32 GetDeviceVolume() const override {
+ return 1.0f;
+ }
+ void SetDeviceVolume(f32 volume) override {}
+ void SetSystemVolume(f32 volume) override {}
+
+private:
+ SinkStreamPtr null_sink{};
+};
+
+} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp
new file mode 100644
index 000000000..1bd001b94
--- /dev/null
+++ b/src/audio_core/sink/sdl2_sink.cpp
@@ -0,0 +1,243 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <span>
+#include <vector>
+
+#include "audio_core/common/common.h"
+#include "audio_core/sink/sdl2_sink.h"
+#include "audio_core/sink/sink_stream.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+
+// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
+#endif
+#include <SDL.h>
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+namespace AudioCore::Sink {
+/**
+ * SDL sink stream, responsible for sinking samples to hardware.
+ */
+class SDLSinkStream final : public SinkStream {
+public:
+ /**
+ * Create a new sink stream.
+ *
+ * @param device_channels_ - Number of channels supported by the hardware.
+ * @param system_channels_ - Number of channels the audio systems expect.
+ * @param output_device - Name of the output device to use for this stream.
+ * @param input_device - Name of the input device to use for this stream.
+ * @param type_ - Type of this stream.
+ * @param system_ - Core system.
+ * @param event - Event used only for audio renderer, signalled on buffer consume.
+ */
+ SDLSinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device,
+ const std::string& input_device, StreamType type_, Core::System& system_)
+ : SinkStream{system_, type_} {
+ system_channels = system_channels_;
+ device_channels = device_channels_;
+
+ SDL_AudioSpec spec;
+ spec.freq = TargetSampleRate;
+ spec.channels = static_cast<u8>(device_channels);
+ spec.format = AUDIO_S16SYS;
+ if (type == StreamType::Render) {
+ spec.samples = TargetSampleCount;
+ } else {
+ spec.samples = 1024;
+ }
+ spec.callback = &SDLSinkStream::DataCallback;
+ spec.userdata = this;
+
+ std::string device_name{output_device};
+ bool capture{false};
+ if (type == StreamType::In) {
+ device_name = input_device;
+ capture = true;
+ }
+
+ SDL_AudioSpec obtained;
+ if (device_name.empty()) {
+ device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false);
+ } else {
+ device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false);
+ }
+
+ if (device == 0) {
+ LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError());
+ return;
+ }
+
+ LOG_INFO(Service_Audio,
+ "Opening SDL stream {} with: rate {} channels {} (system channels {}) "
+ " samples {}",
+ device, obtained.freq, obtained.channels, system_channels, obtained.samples);
+ }
+
+ /**
+ * Destroy the sink stream.
+ */
+ ~SDLSinkStream() override {
+ LOG_DEBUG(Service_Audio, "Destructing SDL stream {}", name);
+ Finalize();
+ }
+
+ /**
+ * Finalize the sink stream.
+ */
+ void Finalize() override {
+ Unstall();
+ if (device == 0) {
+ return;
+ }
+
+ Stop();
+ SDL_CloseAudioDevice(device);
+ }
+
+ /**
+ * Start the sink stream.
+ *
+ * @param resume - Set to true if this is resuming the stream a previously-active stream.
+ * Default false.
+ */
+ void Start(bool resume = false) override {
+ if (device == 0 || !paused) {
+ return;
+ }
+
+ paused = false;
+ SDL_PauseAudioDevice(device, 0);
+ }
+
+ /**
+ * Stop the sink stream.
+ */
+ void Stop() override {
+ Unstall();
+ if (device == 0 || paused) {
+ return;
+ }
+ paused = true;
+ SDL_PauseAudioDevice(device, 1);
+ }
+
+private:
+ /**
+ * Main callback from SDL. Either expects samples from us (audio render/audio out), or will
+ * provide samples to be copied (audio in).
+ *
+ * @param userdata - Custom data pointer passed along, points to a SDLSinkStream.
+ * @param stream - Buffer of samples to be filled or read.
+ * @param len - Length of the stream in bytes.
+ */
+ static void DataCallback(void* userdata, Uint8* stream, int len) {
+ auto* impl = static_cast<SDLSinkStream*>(userdata);
+
+ if (!impl) {
+ return;
+ }
+
+ const std::size_t num_channels = impl->GetDeviceChannels();
+ const std::size_t frame_size = num_channels;
+ const std::size_t num_frames{len / num_channels / sizeof(s16)};
+
+ if (impl->type == StreamType::In) {
+ std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream),
+ num_frames * frame_size};
+ impl->ProcessAudioIn(input_buffer, num_frames);
+ } else {
+ std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size};
+ impl->ProcessAudioOutAndRender(output_buffer, num_frames);
+ }
+ }
+
+ /// SDL device id of the opened input/output device
+ SDL_AudioDeviceID device{};
+};
+
+SDLSink::SDLSink(std::string_view target_device_name) {
+ if (!SDL_WasInit(SDL_INIT_AUDIO)) {
+ if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
+ LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
+ return;
+ }
+ }
+
+ if (target_device_name != auto_device_name && !target_device_name.empty()) {
+ output_device = target_device_name;
+ } else {
+ output_device.clear();
+ }
+
+ device_channels = 2;
+}
+
+SDLSink::~SDLSink() = default;
+
+SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels,
+ const std::string&, StreamType type) {
+ SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
+ device_channels, system_channels, output_device, input_device, type, system));
+ return stream.get();
+}
+
+void SDLSink::CloseStream(SinkStream* stream) {
+ for (size_t i = 0; i < sink_streams.size(); i++) {
+ if (sink_streams[i].get() == stream) {
+ sink_streams[i].reset();
+ sink_streams.erase(sink_streams.begin() + i);
+ break;
+ }
+ }
+}
+
+void SDLSink::CloseStreams() {
+ sink_streams.clear();
+}
+
+f32 SDLSink::GetDeviceVolume() const {
+ if (sink_streams.empty()) {
+ return 1.0f;
+ }
+
+ return sink_streams[0]->GetDeviceVolume();
+}
+
+void SDLSink::SetDeviceVolume(f32 volume) {
+ for (auto& stream : sink_streams) {
+ stream->SetDeviceVolume(volume);
+ }
+}
+
+void SDLSink::SetSystemVolume(f32 volume) {
+ for (auto& stream : sink_streams) {
+ stream->SetSystemVolume(volume);
+ }
+}
+
+std::vector<std::string> ListSDLSinkDevices(bool capture) {
+ std::vector<std::string> device_list;
+
+ if (!SDL_WasInit(SDL_INIT_AUDIO)) {
+ if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
+ LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
+ return {};
+ }
+ }
+
+ const int device_count = SDL_GetNumAudioDevices(capture);
+ for (int i = 0; i < device_count; ++i) {
+ device_list.emplace_back(SDL_GetAudioDeviceName(i, 0));
+ }
+
+ return device_list;
+}
+
+} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h
new file mode 100644
index 000000000..f01eddc1b
--- /dev/null
+++ b/src/audio_core/sink/sdl2_sink.h
@@ -0,0 +1,90 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "audio_core/sink/sink.h"
+
+namespace Core {
+class System;
+}
+
+namespace AudioCore::Sink {
+class SinkStream;
+
+/**
+ * SDL backend sink, holds multiple output streams and is responsible for sinking samples to
+ * hardware. Used by Audio Render, Audio In and Audio Out.
+ */
+class SDLSink final : public Sink {
+public:
+ explicit SDLSink(std::string_view device_id);
+ ~SDLSink() override;
+
+ /**
+ * Create a new sink stream.
+ *
+ * @param system - Core system.
+ * @param system_channels - Number of channels the audio system expects.
+ * May differ from the device's channel count.
+ * @param name - Name of this stream.
+ * @param type - Type of this stream, render/in/out.
+ *
+ * @return A pointer to the created SinkStream
+ */
+ SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
+ const std::string& name, StreamType type) override;
+
+ /**
+ * Close a given stream.
+ *
+ * @param stream - The stream to close.
+ */
+ void CloseStream(SinkStream* stream) override;
+
+ /**
+ * Close all streams.
+ */
+ void CloseStreams() override;
+
+ /**
+ * Get the device volume. Set from calls to the IAudioDevice service.
+ *
+ * @return Volume of the device.
+ */
+ f32 GetDeviceVolume() const override;
+
+ /**
+ * Set the device volume. Set from calls to the IAudioDevice service.
+ *
+ * @param volume - New volume of the device.
+ */
+ void SetDeviceVolume(f32 volume) override;
+
+ /**
+ * Set the system volume. Comes from the audio system using this stream.
+ *
+ * @param volume - New volume of the system.
+ */
+ void SetSystemVolume(f32 volume) override;
+
+private:
+ /// Name of the output device used by streams
+ std::string output_device;
+ /// Name of the input device used by streams
+ std::string input_device;
+ /// Vector of streams managed by this sink
+ std::vector<SinkStreamPtr> sink_streams;
+};
+
+/**
+ * Get a list of connected devices from SDL.
+ *
+ * @param capture - Return input (capture) devices if true, otherwise output devices.
+ */
+std::vector<std::string> ListSDLSinkDevices(bool capture);
+
+} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h
new file mode 100644
index 000000000..f28c6d126
--- /dev/null
+++ b/src/audio_core/sink/sink.h
@@ -0,0 +1,95 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "audio_core/sink/sink_stream.h"
+#include "common/common_types.h"
+
+namespace Common {
+class Event;
+}
+namespace Core {
+class System;
+}
+
+namespace AudioCore::Sink {
+
+constexpr char auto_device_name[] = "auto";
+
+/**
+ * This class is an interface for an audio sink, holds multiple output streams and is responsible
+ * for sinking samples to hardware. Used by Audio Render, Audio In and Audio Out.
+ */
+class Sink {
+public:
+ virtual ~Sink() = default;
+ /**
+ * Close a given stream.
+ *
+ * @param stream - The stream to close.
+ */
+ virtual void CloseStream(SinkStream* stream) = 0;
+
+ /**
+ * Close all streams.
+ */
+ virtual void CloseStreams() = 0;
+
+ /**
+ * Create a new sink stream, kept within this sink, with a pointer returned for use.
+ * Do not free the returned pointer. When done with the stream, call CloseStream on the sink.
+ *
+ * @param system - Core system.
+ * @param system_channels - Number of channels the audio system expects.
+ * May differ from the device's channel count.
+ * @param name - Name of this stream.
+ * @param type - Type of this stream, render/in/out.
+ *
+ * @return A pointer to the created SinkStream
+ */
+ virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
+ const std::string& name, StreamType type) = 0;
+
+ /**
+ * Get the number of channels the hardware device supports.
+ * Either 2 or 6.
+ *
+ * @return Number of device channels.
+ */
+ u32 GetDeviceChannels() const {
+ return device_channels;
+ }
+
+ /**
+ * Get the device volume. Set from calls to the IAudioDevice service.
+ *
+ * @return Volume of the device.
+ */
+ virtual f32 GetDeviceVolume() const = 0;
+
+ /**
+ * Set the device volume. Set from calls to the IAudioDevice service.
+ *
+ * @param volume - New volume of the device.
+ */
+ virtual void SetDeviceVolume(f32 volume) = 0;
+
+ /**
+ * Set the system volume. Comes from the audio system using this stream.
+ *
+ * @param volume - New volume of the system.
+ */
+ virtual void SetSystemVolume(f32 volume) = 0;
+
+protected:
+ /// Number of device channels supported by the hardware
+ u32 device_channels{2};
+};
+
+using SinkPtr = std::unique_ptr<Sink>;
+
+} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp
new file mode 100644
index 000000000..67bdab779
--- /dev/null
+++ b/src/audio_core/sink/sink_details.cpp
@@ -0,0 +1,91 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "audio_core/sink/sink_details.h"
+#ifdef HAVE_CUBEB
+#include "audio_core/sink/cubeb_sink.h"
+#endif
+#ifdef HAVE_SDL2
+#include "audio_core/sink/sdl2_sink.h"
+#endif
+#include "audio_core/sink/null_sink.h"
+#include "common/logging/log.h"
+
+namespace AudioCore::Sink {
+namespace {
+struct SinkDetails {
+ using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
+ using ListDevicesFn = std::vector<std::string> (*)(bool);
+
+ /// Name for this sink.
+ const char* id;
+ /// A method to call to construct an instance of this type of sink.
+ FactoryFn factory;
+ /// A method to call to list available devices.
+ ListDevicesFn list_devices;
+};
+
+// sink_details is ordered in terms of desirability, with the best choice at the top.
+constexpr SinkDetails sink_details[] = {
+#ifdef HAVE_CUBEB
+ SinkDetails{"cubeb",
+ [](std::string_view device_id) -> std::unique_ptr<Sink> {
+ return std::make_unique<CubebSink>(device_id);
+ },
+ &ListCubebSinkDevices},
+#endif
+#ifdef HAVE_SDL2
+ SinkDetails{"sdl2",
+ [](std::string_view device_id) -> std::unique_ptr<Sink> {
+ return std::make_unique<SDLSink>(device_id);
+ },
+ &ListSDLSinkDevices},
+#endif
+ SinkDetails{"null",
+ [](std::string_view device_id) -> std::unique_ptr<Sink> {
+ return std::make_unique<NullSink>(device_id);
+ },
+ [](bool capture) { return std::vector<std::string>{"null"}; }},
+};
+
+const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) {
+ auto iter =
+ std::find_if(std::begin(sink_details), std::end(sink_details),
+ [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
+
+ if (sink_id == "auto" || iter == std::end(sink_details)) {
+ if (sink_id != "auto") {
+ LOG_ERROR(Audio, "Invalid sink_id {}", sink_id);
+ }
+ // Auto-select.
+ // sink_details is ordered in terms of desirability, with the best choice at the front.
+ iter = std::begin(sink_details);
+ }
+
+ return *iter;
+}
+} // Anonymous namespace
+
+std::vector<const char*> GetSinkIDs() {
+ std::vector<const char*> sink_ids(std::size(sink_details));
+
+ std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids),
+ [](const auto& sink) { return sink.id; });
+
+ return sink_ids;
+}
+
+std::vector<std::string> GetDeviceListForSink(std::string_view sink_id, bool capture) {
+ return GetOutputSinkDetails(sink_id).list_devices(capture);
+}
+
+std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) {
+ return GetOutputSinkDetails(sink_id).factory(device_id);
+}
+
+} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink_details.h b/src/audio_core/sink/sink_details.h
new file mode 100644
index 000000000..3ebdb1e30
--- /dev/null
+++ b/src/audio_core/sink/sink_details.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace AudioCore {
+class AudioManager;
+
+namespace Sink {
+
+class Sink;
+
+/**
+ * Retrieves the IDs for all available audio sinks.
+ *
+ * @return Vector of available sink names.
+ */
+std::vector<const char*> GetSinkIDs();
+
+/**
+ * Gets the list of devices for a particular sink identified by the given ID.
+ *
+ * @param sink_id - Id of the sink to get devices from.
+ * @param capture - Get capture (input) devices, or output devices?
+ * @return Vector of device names.
+ */
+std::vector<std::string> GetDeviceListForSink(std::string_view sink_id, bool capture);
+
+/**
+ * Creates an audio sink identified by the given device ID.
+ *
+ * @param sink_id - Id of the sink to create.
+ * @param device_id - Name of the device to create.
+ * @return Pointer to the created sink.
+ */
+std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id);
+
+} // namespace Sink
+} // namespace AudioCore
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
new file mode 100644
index 000000000..849f862b0
--- /dev/null
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -0,0 +1,284 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <atomic>
+#include <memory>
+#include <span>
+#include <vector>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/common/common.h"
+#include "audio_core/sink/sink_stream.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+#include "common/settings.h"
+#include "core/core.h"
+
+namespace AudioCore::Sink {
+
+void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
+ if (type == StreamType::In) {
+ queue.enqueue(buffer);
+ queued_buffers++;
+ return;
+ }
+
+ constexpr s32 min{std::numeric_limits<s16>::min()};
+ constexpr s32 max{std::numeric_limits<s16>::max()};
+
+ auto yuzu_volume{Settings::Volume()};
+ if (yuzu_volume > 1.0f) {
+ yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume);
+ }
+ auto volume{system_volume * device_volume * yuzu_volume};
+
+ if (system_channels == 6 && device_channels == 2) {
+ // We're given 6 channels, but our device only outputs 2, so downmix.
+ constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
+
+ for (u32 read_index = 0, write_index = 0; read_index < samples.size();
+ read_index += system_channels, write_index += device_channels) {
+ const auto left_sample{
+ ((Common::FixedPoint<49, 15>(
+ samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
+ down_mix_coeff[0] +
+ samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
+ samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
+ samples[read_index + static_cast<u32>(Channels::BackLeft)] * down_mix_coeff[3]) *
+ volume)
+ .to_int()};
+
+ const auto right_sample{
+ ((Common::FixedPoint<49, 15>(
+ samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
+ down_mix_coeff[0] +
+ samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
+ samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
+ samples[read_index + static_cast<u32>(Channels::BackRight)] * down_mix_coeff[3]) *
+ volume)
+ .to_int()};
+
+ samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
+ static_cast<s16>(std::clamp(left_sample, min, max));
+ samples[write_index + static_cast<u32>(Channels::FrontRight)] =
+ static_cast<s16>(std::clamp(right_sample, min, max));
+ }
+
+ samples.resize(samples.size() / system_channels * device_channels);
+
+ } else if (system_channels == 2 && device_channels == 6) {
+ // We need moar samples! Not all games will provide 6 channel audio.
+ // TODO: Implement some upmixing here. Currently just passthrough, with other
+ // channels left as silence.
+ std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
+
+ for (u32 read_index = 0, write_index = 0; read_index < samples.size();
+ read_index += system_channels, write_index += device_channels) {
+ const auto left_sample{static_cast<s16>(std::clamp(
+ static_cast<s32>(
+ static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
+ volume),
+ min, max))};
+
+ new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
+
+ const auto right_sample{static_cast<s16>(std::clamp(
+ static_cast<s32>(
+ static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
+ volume),
+ min, max))};
+
+ new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample;
+ }
+ samples = std::move(new_samples);
+
+ } else if (volume != 1.0f) {
+ for (u32 i = 0; i < samples.size(); i++) {
+ samples[i] = static_cast<s16>(
+ std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
+ }
+ }
+
+ samples_buffer.Push(samples);
+ queue.enqueue(buffer);
+ queued_buffers++;
+}
+
+std::vector<s16> SinkStream::ReleaseBuffer(u64 num_samples) {
+ constexpr s32 min = std::numeric_limits<s16>::min();
+ constexpr s32 max = std::numeric_limits<s16>::max();
+
+ auto samples{samples_buffer.Pop(num_samples)};
+
+ // TODO: Up-mix to 6 channels if the game expects it.
+ // For audio input this is unlikely to ever be the case though.
+
+ // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
+ // TODO: Play with this and find something that works better.
+ auto volume{system_volume * device_volume * 8};
+ for (u32 i = 0; i < samples.size(); i++) {
+ samples[i] = static_cast<s16>(
+ std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
+ }
+
+ if (samples.size() < num_samples) {
+ samples.resize(num_samples, 0);
+ }
+ return samples;
+}
+
+void SinkStream::ClearQueue() {
+ samples_buffer.Pop();
+ while (queue.pop()) {
+ }
+ queued_buffers = 0;
+ playing_buffer = {};
+ playing_buffer.consumed = true;
+}
+
+void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames) {
+ const std::size_t num_channels = GetDeviceChannels();
+ const std::size_t frame_size = num_channels;
+ const std::size_t frame_size_bytes = frame_size * sizeof(s16);
+ size_t frames_written{0};
+
+ // If we're paused or going to shut down, we don't want to consume buffers as coretiming is
+ // paused and we'll desync, so just return.
+ if (system.IsPaused() || system.IsShuttingDown()) {
+ return;
+ }
+
+ if (queued_buffers > max_queue_size) {
+ Stall();
+ }
+
+ while (frames_written < num_frames) {
+ // If the playing buffer has been consumed or has no frames, we need a new one
+ if (playing_buffer.consumed || playing_buffer.frames == 0) {
+ if (!queue.try_dequeue(playing_buffer)) {
+ // If no buffer was available we've underrun, just push the samples and
+ // continue.
+ samples_buffer.Push(&input_buffer[frames_written * frame_size],
+ (num_frames - frames_written) * frame_size);
+ frames_written = num_frames;
+ continue;
+ }
+ // Successfully dequeued a new buffer.
+ queued_buffers--;
+ }
+
+ // Get the minimum frames available between the currently playing buffer, and the
+ // amount we have left to fill
+ size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played,
+ num_frames - frames_written)};
+
+ samples_buffer.Push(&input_buffer[frames_written * frame_size],
+ frames_available * frame_size);
+
+ frames_written += frames_available;
+ playing_buffer.frames_played += frames_available;
+
+ // If that's all the frames in the current buffer, add its samples and mark it as
+ // consumed
+ if (playing_buffer.frames_played >= playing_buffer.frames) {
+ playing_buffer.consumed = true;
+ }
+ }
+
+ std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes);
+
+ if (queued_buffers <= max_queue_size) {
+ Unstall();
+ }
+}
+
+void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames) {
+ const std::size_t num_channels = GetDeviceChannels();
+ const std::size_t frame_size = num_channels;
+ const std::size_t frame_size_bytes = frame_size * sizeof(s16);
+ size_t frames_written{0};
+
+ // If we're paused or going to shut down, we don't want to consume buffers as coretiming is
+ // paused and we'll desync, so just play silence.
+ if (system.IsPaused() || system.IsShuttingDown()) {
+ constexpr std::array<s16, 6> silence{};
+ for (size_t i = frames_written; i < num_frames; i++) {
+ std::memcpy(&output_buffer[i * frame_size], &silence[0], frame_size_bytes);
+ }
+ return;
+ }
+
+ // Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get
+ // queued up (30+) but not all at once, which causes constant stalling here, so just let the
+ // video play out without attempting to stall.
+ // Can hopefully remove this later with a more complete NVDEC implementation.
+ const auto nvdec_active{system.AudioCore().IsNVDECActive()};
+
+ // Core timing cannot be paused in single-core mode, so Stall ends up being called over and over
+ // and never recovers to a normal state, so just skip attempting to sync things on single-core.
+ if (system.IsMulticore() && !nvdec_active && queued_buffers > max_queue_size) {
+ Stall();
+ } else if (system.IsMulticore() && queued_buffers <= max_queue_size) {
+ Unstall();
+ }
+
+ while (frames_written < num_frames) {
+ // If the playing buffer has been consumed or has no frames, we need a new one
+ if (playing_buffer.consumed || playing_buffer.frames == 0) {
+ if (!queue.try_dequeue(playing_buffer)) {
+ // If no buffer was available we've underrun, fill the remaining buffer with
+ // the last written frame and continue.
+ for (size_t i = frames_written; i < num_frames; i++) {
+ std::memcpy(&output_buffer[i * frame_size], &last_frame[0], frame_size_bytes);
+ }
+ frames_written = num_frames;
+ continue;
+ }
+ // Successfully dequeued a new buffer.
+ queued_buffers--;
+ }
+
+ // Get the minimum frames available between the currently playing buffer, and the
+ // amount we have left to fill
+ size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played,
+ num_frames - frames_written)};
+
+ samples_buffer.Pop(&output_buffer[frames_written * frame_size],
+ frames_available * frame_size);
+
+ frames_written += frames_available;
+ playing_buffer.frames_played += frames_available;
+
+ // If that's all the frames in the current buffer, add its samples and mark it as
+ // consumed
+ if (playing_buffer.frames_played >= playing_buffer.frames) {
+ playing_buffer.consumed = true;
+ }
+ }
+
+ std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
+ frame_size_bytes);
+
+ if (system.IsMulticore() && queued_buffers <= max_queue_size) {
+ Unstall();
+ }
+}
+
+void SinkStream::Stall() {
+ if (stalled) {
+ return;
+ }
+ stalled = true;
+ system.StallProcesses();
+}
+
+void SinkStream::Unstall() {
+ if (!stalled) {
+ return;
+ }
+ system.UnstallProcesses();
+ stalled = false;
+}
+
+} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h
new file mode 100644
index 000000000..38a4b2f51
--- /dev/null
+++ b/src/audio_core/sink/sink_stream.h
@@ -0,0 +1,249 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <memory>
+#include <span>
+#include <vector>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+#include "common/reader_writer_queue.h"
+#include "common/ring_buffer.h"
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace AudioCore::Sink {
+
+enum class StreamType {
+ Render,
+ Out,
+ In,
+};
+
+struct SinkBuffer {
+ u64 frames;
+ u64 frames_played;
+ u64 tag;
+ bool consumed;
+};
+
+/**
+ * Contains a real backend stream for outputting samples to hardware,
+ * created only via a Sink (See Sink::AcquireSinkStream).
+ *
+ * Accepts a SinkBuffer and samples in PCM16 format to be output (see AppendBuffer).
+ * Appended buffers act as a FIFO queue, and will be held until played.
+ * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer
+ * has been consumed.
+ *
+ * Since these are a FIFO queue, IsBufferConsumed must be checked in the same order buffers were
+ * appended, skipping a buffer will result in the queue getting stuck, and all following buffers to
+ * never release.
+ *
+ * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this
+ * is what games do), or call ClearQueue to flush all of the buffers without a full restart.
+ */
+class SinkStream {
+public:
+ explicit SinkStream(Core::System& system_, StreamType type_) : system{system_}, type{type_} {}
+ virtual ~SinkStream() {
+ Unstall();
+ }
+
+ /**
+ * Finalize the sink stream.
+ */
+ virtual void Finalize() {}
+
+ /**
+ * Start the sink stream.
+ *
+ * @param resume - Set to true if this is resuming the stream a previously-active stream.
+ * Default false.
+ */
+ virtual void Start(bool resume = false) {}
+
+ /**
+ * Stop the sink stream.
+ */
+ virtual void Stop() {}
+
+ /**
+ * Check if the stream is paused.
+ *
+ * @return True if paused, otherwise false.
+ */
+ bool IsPaused() const {
+ return paused;
+ }
+
+ /**
+ * Get the number of system channels in this stream.
+ *
+ * @return Number of system channels.
+ */
+ u32 GetSystemChannels() const {
+ return system_channels;
+ }
+
+ /**
+ * Set the number of channels the system expects.
+ *
+ * @param channels - New number of system channels.
+ */
+ void SetSystemChannels(u32 channels) {
+ system_channels = channels;
+ }
+
+ /**
+ * Get the number of channels the hardware supports.
+ *
+ * @return Number of channels supported.
+ */
+ u32 GetDeviceChannels() const {
+ return device_channels;
+ }
+
+ /**
+ * Get the system volume.
+ *
+ * @return The current system volume.
+ */
+ f32 GetSystemVolume() const {
+ return system_volume;
+ }
+
+ /**
+ * Get the device volume.
+ *
+ * @return The current device volume.
+ */
+ f32 GetDeviceVolume() const {
+ return device_volume;
+ }
+
+ /**
+ * Set the system volume.
+ *
+ * @param volume_ - The new system volume.
+ */
+ void SetSystemVolume(f32 volume_) {
+ system_volume = volume_;
+ }
+
+ /**
+ * Set the device volume.
+ *
+ * @param volume_ - The new device volume.
+ */
+ void SetDeviceVolume(f32 volume_) {
+ device_volume = volume_;
+ }
+
+ /**
+ * Get the number of queued audio buffers.
+ *
+ * @return The number of queued buffers.
+ */
+ u32 GetQueueSize() const {
+ return queued_buffers.load();
+ }
+
+ /**
+ * Set the maximum buffer queue size.
+ */
+ void SetRingSize(u32 ring_size) {
+ max_queue_size = ring_size;
+ }
+
+ /**
+ * Append a new buffer and its samples to a waiting queue to play.
+ *
+ * @param buffer - Audio buffer information to be queued.
+ * @param samples - The s16 samples to be queue for playback.
+ */
+ virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples);
+
+ /**
+ * Release a buffer. Audio In only, will fill a buffer with recorded samples.
+ *
+ * @param num_samples - Maximum number of samples to receive.
+ * @return Vector of recorded samples. May have fewer than num_samples.
+ */
+ virtual std::vector<s16> ReleaseBuffer(u64 num_samples);
+
+ /**
+ * Empty out the buffer queue.
+ */
+ void ClearQueue();
+
+ /**
+ * Callback for AudioIn.
+ *
+ * @param input_buffer - Input buffer to be filled with samples.
+ * @param num_frames - Number of frames to be filled.
+ */
+ void ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames);
+
+ /**
+ * Callback for AudioOut and AudioRenderer.
+ *
+ * @param output_buffer - Output buffer to be filled with samples.
+ * @param num_frames - Number of frames to be filled.
+ */
+ void ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames);
+
+ /**
+ * Stall core processes if the audio thread falls too far behind.
+ */
+ void Stall();
+
+ /**
+ * Unstall core processes.
+ */
+ void Unstall();
+
+protected:
+ /// Core system
+ Core::System& system;
+ /// Type of this stream
+ StreamType type;
+ /// Set by the audio render/in/out system which uses this stream
+ u32 system_channels{2};
+ /// Channels supported by hardware
+ u32 device_channels{2};
+ /// Is this stream currently paused?
+ std::atomic<bool> paused{true};
+ /// Name of this stream
+ std::string name{};
+
+private:
+ /// Ring buffer of the samples waiting to be played or consumed
+ Common::RingBuffer<s16, 0x10000> samples_buffer;
+ /// Audio buffers queued and waiting to play
+ Common::ReaderWriterQueue<SinkBuffer> queue;
+ /// The currently-playing audio buffer
+ SinkBuffer playing_buffer{};
+ /// The last played (or received) frame of audio, used when the callback underruns
+ std::array<s16, MaxChannels> last_frame{};
+ /// Number of buffers waiting to be played
+ std::atomic<u32> queued_buffers{};
+ /// The ring size for audio out buffers (usually 4, rarely 2 or 8)
+ u32 max_queue_size{};
+ /// Set by the audio render/in/out system which uses this stream
+ f32 system_volume{1.0f};
+ /// Set via IAudioDevice service calls
+ f32 device_volume{1.0f};
+ /// True if coretiming has been stalled
+ bool stalled{false};
+};
+
+using SinkStreamPtr = std::unique_ptr<SinkStream>;
+
+} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink_context.cpp b/src/audio_core/sink_context.cpp
deleted file mode 100644
index cc55b290c..000000000
--- a/src/audio_core/sink_context.cpp
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "audio_core/sink_context.h"
-
-namespace AudioCore {
-SinkContext::SinkContext(std::size_t sink_count_) : sink_count{sink_count_} {}
-SinkContext::~SinkContext() = default;
-
-std::size_t SinkContext::GetCount() const {
- return sink_count;
-}
-
-void SinkContext::UpdateMainSink(const SinkInfo::InParams& in) {
- ASSERT(in.type == SinkTypes::Device);
-
- if (in.device.down_matrix_enabled) {
- downmix_coefficients = in.device.down_matrix_coef;
- } else {
- downmix_coefficients = {
- 1.0f, // front
- 0.707f, // center
- 0.0f, // lfe
- 0.707f, // back
- };
- }
-
- in_use = in.in_use;
- use_count = in.device.input_count;
- buffers = in.device.input;
-}
-
-bool SinkContext::InUse() const {
- return in_use;
-}
-
-std::vector<u8> SinkContext::OutputBuffers() const {
- std::vector<u8> buffer_ret(use_count);
- std::memcpy(buffer_ret.data(), buffers.data(), use_count);
- return buffer_ret;
-}
-
-const DownmixCoefficients& SinkContext::GetDownmixCoefficients() const {
- return downmix_coefficients;
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h
deleted file mode 100644
index 254961fe2..000000000
--- a/src/audio_core/sink_context.h
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <vector>
-#include "audio_core/common.h"
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/swap.h"
-
-namespace AudioCore {
-
-using DownmixCoefficients = std::array<float_le, 4>;
-
-enum class SinkTypes : u8 {
- Invalid = 0,
- Device = 1,
- Circular = 2,
-};
-
-enum class SinkSampleFormat : u32_le {
- None = 0,
- Pcm8 = 1,
- Pcm16 = 2,
- Pcm24 = 3,
- Pcm32 = 4,
- PcmFloat = 5,
- Adpcm = 6,
-};
-
-class SinkInfo {
-public:
- struct CircularBufferIn {
- u64_le address;
- u32_le size;
- u32_le input_count;
- u32_le sample_count;
- u32_le previous_position;
- SinkSampleFormat sample_format;
- std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
- bool in_use;
- INSERT_PADDING_BYTES_NOINIT(5);
- };
- static_assert(sizeof(CircularBufferIn) == 0x28,
- "SinkInfo::CircularBufferIn is in invalid size");
-
- struct DeviceIn {
- std::array<u8, 255> device_name;
- INSERT_PADDING_BYTES_NOINIT(1);
- s32_le input_count;
- std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
- INSERT_PADDING_BYTES_NOINIT(1);
- bool down_matrix_enabled;
- DownmixCoefficients down_matrix_coef;
- };
- static_assert(sizeof(DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size");
-
- struct InParams {
- SinkTypes type{};
- bool in_use{};
- INSERT_PADDING_BYTES(2);
- u32_le node_id{};
- INSERT_PADDING_WORDS(6);
- union {
- // std::array<u8, 0x120> raw{};
- DeviceIn device;
- CircularBufferIn circular_buffer;
- };
- };
- static_assert(sizeof(InParams) == 0x140, "SinkInfo::InParams are an invalid size!");
-};
-
-class SinkContext {
-public:
- explicit SinkContext(std::size_t sink_count_);
- ~SinkContext();
-
- [[nodiscard]] std::size_t GetCount() const;
-
- void UpdateMainSink(const SinkInfo::InParams& in);
- [[nodiscard]] bool InUse() const;
- [[nodiscard]] std::vector<u8> OutputBuffers() const;
-
- [[nodiscard]] const DownmixCoefficients& GetDownmixCoefficients() const;
-
-private:
- bool in_use{false};
- s32 use_count{};
- std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{};
- std::size_t sink_count{};
- DownmixCoefficients downmix_coefficients{};
-};
-} // namespace AudioCore
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp
deleted file mode 100644
index de10aecd2..000000000
--- a/src/audio_core/sink_details.cpp
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <memory>
-#include <string>
-#include <vector>
-#include "audio_core/null_sink.h"
-#include "audio_core/sink_details.h"
-#ifdef HAVE_CUBEB
-#include "audio_core/cubeb_sink.h"
-#endif
-#ifdef HAVE_SDL2
-#include "audio_core/sdl2_sink.h"
-#endif
-#include "common/logging/log.h"
-
-namespace AudioCore {
-namespace {
-struct SinkDetails {
- using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
- using ListDevicesFn = std::vector<std::string> (*)();
-
- /// Name for this sink.
- const char* id;
- /// A method to call to construct an instance of this type of sink.
- FactoryFn factory;
- /// A method to call to list available devices.
- ListDevicesFn list_devices;
-};
-
-// sink_details is ordered in terms of desirability, with the best choice at the top.
-constexpr SinkDetails sink_details[] = {
-#ifdef HAVE_CUBEB
- SinkDetails{"cubeb",
- [](std::string_view device_id) -> std::unique_ptr<Sink> {
- return std::make_unique<CubebSink>(device_id);
- },
- &ListCubebSinkDevices},
-#endif
-#ifdef HAVE_SDL2
- SinkDetails{"sdl2",
- [](std::string_view device_id) -> std::unique_ptr<Sink> {
- return std::make_unique<SDLSink>(device_id);
- },
- &ListSDLSinkDevices},
-#endif
- SinkDetails{"null",
- [](std::string_view device_id) -> std::unique_ptr<Sink> {
- return std::make_unique<NullSink>(device_id);
- },
- [] { return std::vector<std::string>{"null"}; }},
-};
-
-const SinkDetails& GetSinkDetails(std::string_view sink_id) {
- auto iter =
- std::find_if(std::begin(sink_details), std::end(sink_details),
- [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
-
- if (sink_id == "auto" || iter == std::end(sink_details)) {
- if (sink_id != "auto") {
- LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id);
- }
- // Auto-select.
- // sink_details is ordered in terms of desirability, with the best choice at the front.
- iter = std::begin(sink_details);
- }
-
- return *iter;
-}
-} // Anonymous namespace
-
-std::vector<const char*> GetSinkIDs() {
- std::vector<const char*> sink_ids(std::size(sink_details));
-
- std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids),
- [](const auto& sink) { return sink.id; });
-
- return sink_ids;
-}
-
-std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) {
- return GetSinkDetails(sink_id).list_devices();
-}
-
-std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) {
- return GetSinkDetails(sink_id).factory(device_id);
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h
deleted file mode 100644
index bc8786270..000000000
--- a/src/audio_core/sink_details.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <string>
-#include <string_view>
-#include <vector>
-
-namespace AudioCore {
-
-class Sink;
-
-/// Retrieves the IDs for all available audio sinks.
-std::vector<const char*> GetSinkIDs();
-
-/// Gets the list of devices for a particular sink identified by the given ID.
-std::vector<std::string> GetDeviceListForSink(std::string_view sink_id);
-
-/// Creates an audio sink identified by the given device ID.
-std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id);
-
-} // namespace AudioCore
diff --git a/src/audio_core/sink_stream.h b/src/audio_core/sink_stream.h
deleted file mode 100644
index 4309ad094..000000000
--- a/src/audio_core/sink_stream.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <vector>
-
-#include "common/common_types.h"
-
-namespace AudioCore {
-
-/**
- * Accepts samples in stereo signed PCM16 format to be output. Sinks *do not* handle resampling and
- * expect the correct sample rate. They are dumb outputs.
- */
-class SinkStream {
-public:
- virtual ~SinkStream() = default;
-
- /**
- * Feed stereo samples to sink.
- * @param num_channels Number of channels used.
- * @param samples Samples in interleaved stereo PCM16 format.
- */
- virtual void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) = 0;
-
- virtual std::size_t SamplesInQueue(u32 num_channels) const = 0;
-
- virtual void Flush() = 0;
-};
-
-using SinkStreamPtr = std::unique_ptr<SinkStream>;
-
-} // namespace AudioCore
diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp
deleted file mode 100644
index f4bcd0391..000000000
--- a/src/audio_core/splitter_context.cpp
+++ /dev/null
@@ -1,617 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "audio_core/behavior_info.h"
-#include "audio_core/splitter_context.h"
-#include "common/alignment.h"
-#include "common/assert.h"
-#include "common/logging/log.h"
-
-namespace AudioCore {
-
-ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id_) : id{id_} {}
-ServerSplitterDestinationData::~ServerSplitterDestinationData() = default;
-
-void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) {
- // Log error as these are not actually failure states
- if (header.magic != SplitterMagic::DataHeader) {
- LOG_ERROR(Audio, "Splitter destination header is invalid!");
- return;
- }
-
- // Incorrect splitter id
- if (header.splitter_id != id) {
- LOG_ERROR(Audio, "Splitter destination ids do not match!");
- return;
- }
-
- mix_id = header.mix_id;
- // Copy our mix volumes
- std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin());
- if (!in_use && header.in_use) {
- // Update mix volumes
- std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
- needs_update = false;
- }
- in_use = header.in_use;
-}
-
-ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() {
- return next;
-}
-
-const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const {
- return next;
-}
-
-void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) {
- next = dest;
-}
-
-bool ServerSplitterDestinationData::ValidMixId() const {
- return GetMixId() != AudioCommon::NO_MIX;
-}
-
-s32 ServerSplitterDestinationData::GetMixId() const {
- return mix_id;
-}
-
-bool ServerSplitterDestinationData::IsConfigured() const {
- return in_use && ValidMixId();
-}
-
-float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const {
- ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
- return current_mix_volumes.at(i);
-}
-
-const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
-ServerSplitterDestinationData::CurrentMixVolumes() const {
- return current_mix_volumes;
-}
-
-const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
-ServerSplitterDestinationData::LastMixVolumes() const {
- return last_mix_volumes;
-}
-
-void ServerSplitterDestinationData::MarkDirty() {
- needs_update = true;
-}
-
-void ServerSplitterDestinationData::UpdateInternalState() {
- if (in_use && needs_update) {
- std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
- }
- needs_update = false;
-}
-
-ServerSplitterInfo::ServerSplitterInfo(s32 id_) : id(id_) {}
-ServerSplitterInfo::~ServerSplitterInfo() = default;
-
-void ServerSplitterInfo::InitializeInfos() {
- send_length = 0;
- head = nullptr;
- new_connection = true;
-}
-
-void ServerSplitterInfo::ClearNewConnectionFlag() {
- new_connection = false;
-}
-
-std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) {
- if (header.send_id != id) {
- return 0;
- }
-
- sample_rate = header.sample_rate;
- new_connection = true;
- // We need to update the size here due to the splitter bug being present and providing an
- // incorrect size. We're suppose to also update the header here but we just ignore and continue
- return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3);
-}
-
-ServerSplitterDestinationData* ServerSplitterInfo::GetHead() {
- return head;
-}
-
-const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const {
- return head;
-}
-
-ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) {
- auto* current_head = head;
- for (std::size_t i = 0; i < depth; i++) {
- if (current_head == nullptr) {
- return nullptr;
- }
- current_head = current_head->GetNextDestination();
- }
- return current_head;
-}
-
-const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const {
- auto* current_head = head;
- for (std::size_t i = 0; i < depth; i++) {
- if (current_head == nullptr) {
- return nullptr;
- }
- current_head = current_head->GetNextDestination();
- }
- return current_head;
-}
-
-bool ServerSplitterInfo::HasNewConnection() const {
- return new_connection;
-}
-
-s32 ServerSplitterInfo::GetLength() const {
- return send_length;
-}
-
-void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) {
- head = new_head;
-}
-
-void ServerSplitterInfo::SetHeadDepth(s32 length) {
- send_length = length;
-}
-
-SplitterContext::SplitterContext() = default;
-SplitterContext::~SplitterContext() = default;
-
-void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count,
- std::size_t _data_count) {
- if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) {
- Setup(0, 0, false);
- return;
- }
- // Only initialize if we're using splitters
- Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed());
-}
-
-bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset,
- std::size_t& bytes_read) {
- const auto UpdateOffsets = [&](std::size_t read) {
- input_offset += read;
- bytes_read += read;
- };
-
- if (info_count == 0 || data_count == 0) {
- bytes_read = 0;
- return true;
- }
-
- if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
- sizeof(SplitterInfo::InHeader))) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
- SplitterInfo::InHeader header{};
- std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader));
- UpdateOffsets(sizeof(SplitterInfo::InHeader));
-
- if (header.magic != SplitterMagic::SplitterHeader) {
- LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}",
- SplitterMagic::SplitterHeader, header.magic);
- return false;
- }
-
- // Clear all connections
- for (auto& info : infos) {
- info.ClearNewConnectionFlag();
- }
-
- UpdateInfo(input, input_offset, bytes_read, header.info_count);
- UpdateData(input, input_offset, bytes_read, header.data_count);
- const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16);
- input_offset += aligned_bytes_read - bytes_read;
- bytes_read = aligned_bytes_read;
- return true;
-}
-
-bool SplitterContext::UsingSplitter() const {
- return info_count > 0 && data_count > 0;
-}
-
-ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) {
- ASSERT(i < info_count);
- return infos.at(i);
-}
-
-const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const {
- ASSERT(i < info_count);
- return infos.at(i);
-}
-
-ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) {
- ASSERT(i < data_count);
- return datas.at(i);
-}
-
-const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const {
- ASSERT(i < data_count);
- return datas.at(i);
-}
-
-ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
- std::size_t data) {
- ASSERT(info < info_count);
- auto& cur_info = GetInfo(info);
- return cur_info.GetData(data);
-}
-
-const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
- std::size_t data) const {
- ASSERT(info < info_count);
- const auto& cur_info = GetInfo(info);
- return cur_info.GetData(data);
-}
-
-void SplitterContext::UpdateInternalState() {
- if (data_count == 0) {
- return;
- }
-
- for (auto& data : datas) {
- data.UpdateInternalState();
- }
-}
-
-std::size_t SplitterContext::GetInfoCount() const {
- return info_count;
-}
-
-std::size_t SplitterContext::GetDataCount() const {
- return data_count;
-}
-
-void SplitterContext::Setup(std::size_t info_count_, std::size_t data_count_,
- bool is_splitter_bug_fixed) {
-
- info_count = info_count_;
- data_count = data_count_;
-
- for (std::size_t i = 0; i < info_count; i++) {
- auto& splitter = infos.emplace_back(static_cast<s32>(i));
- splitter.InitializeInfos();
- }
- for (std::size_t i = 0; i < data_count; i++) {
- datas.emplace_back(static_cast<s32>(i));
- }
-
- bug_fixed = is_splitter_bug_fixed;
-}
-
-bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
- std::size_t& bytes_read, s32 in_splitter_count) {
- const auto UpdateOffsets = [&](std::size_t read) {
- input_offset += read;
- bytes_read += read;
- };
-
- for (s32 i = 0; i < in_splitter_count; i++) {
- if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
- sizeof(SplitterInfo::InInfoPrams))) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
- SplitterInfo::InInfoPrams header{};
- std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams));
-
- // Logged as warning as these don't actually cause a bailout for some reason
- if (header.magic != SplitterMagic::InfoHeader) {
- LOG_ERROR(Audio, "Bad splitter data header");
- break;
- }
-
- if (header.send_id < 0 || static_cast<std::size_t>(header.send_id) > info_count) {
- LOG_ERROR(Audio, "Bad splitter data id");
- break;
- }
-
- UpdateOffsets(sizeof(SplitterInfo::InInfoPrams));
- auto& info = GetInfo(header.send_id);
- if (!RecomposeDestination(info, header, input, input_offset)) {
- LOG_ERROR(Audio, "Failed to recompose destination for splitter!");
- return false;
- }
- const std::size_t read = info.Update(header);
- bytes_read += read;
- input_offset += read;
- }
- return true;
-}
-
-bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
- std::size_t& bytes_read, s32 in_data_count) {
- const auto UpdateOffsets = [&](std::size_t read) {
- input_offset += read;
- bytes_read += read;
- };
-
- for (s32 i = 0; i < in_data_count; i++) {
- if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
- sizeof(SplitterInfo::InDestinationParams))) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
- SplitterInfo::InDestinationParams header{};
- std::memcpy(&header, input.data() + input_offset,
- sizeof(SplitterInfo::InDestinationParams));
- UpdateOffsets(sizeof(SplitterInfo::InDestinationParams));
-
- // Logged as warning as these don't actually cause a bailout for some reason
- if (header.magic != SplitterMagic::DataHeader) {
- LOG_ERROR(Audio, "Bad splitter data header");
- break;
- }
-
- if (header.splitter_id < 0 || static_cast<std::size_t>(header.splitter_id) > data_count) {
- LOG_ERROR(Audio, "Bad splitter data id");
- break;
- }
- GetData(header.splitter_id).Update(header);
- }
- return true;
-}
-
-bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info,
- SplitterInfo::InInfoPrams& header,
- const std::vector<u8>& input,
- const std::size_t& input_offset) {
- // Clear our current destinations
- auto* current_head = info.GetHead();
- while (current_head != nullptr) {
- auto* next_head = current_head->GetNextDestination();
- current_head->SetNextDestination(nullptr);
- current_head = next_head;
- }
- info.SetHead(nullptr);
-
- s32 size = header.length;
- // If the splitter bug is present, calculate fixed size
- if (!bug_fixed) {
- if (info_count > 0) {
- const auto factor = data_count / info_count;
- size = std::min(header.length, static_cast<s32>(factor));
- } else {
- size = 0;
- }
- }
-
- if (size < 1) {
- LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size);
- return true;
- }
-
- auto* start_head = &GetData(header.resource_id_base);
- current_head = start_head;
- std::vector<s32_le> resource_ids(size - 1);
- if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
- resource_ids.size() * sizeof(s32_le))) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
- std::memcpy(resource_ids.data(), input.data() + input_offset,
- resource_ids.size() * sizeof(s32_le));
-
- for (auto resource_id : resource_ids) {
- auto* head = &GetData(resource_id);
- current_head->SetNextDestination(head);
- current_head = head;
- }
-
- info.SetHead(start_head);
- info.SetHeadDepth(size);
-
- return true;
-}
-
-NodeStates::NodeStates() = default;
-NodeStates::~NodeStates() = default;
-
-void NodeStates::Initialize(std::size_t node_count_) {
- // Setup our work parameters
- node_count = node_count_;
- was_node_found.resize(node_count);
- was_node_completed.resize(node_count);
- index_list.resize(node_count);
- index_stack.Reset(node_count * node_count);
-}
-
-bool NodeStates::Tsort(EdgeMatrix& edge_matrix) {
- return DepthFirstSearch(edge_matrix);
-}
-
-std::size_t NodeStates::GetIndexPos() const {
- return index_pos;
-}
-
-const std::vector<s32>& NodeStates::GetIndexList() const {
- return index_list;
-}
-
-void NodeStates::PushTsortResult(s32 index) {
- ASSERT(index < static_cast<s32>(node_count));
- index_list[index_pos++] = index;
-}
-
-bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) {
- ResetState();
- for (std::size_t i = 0; i < node_count; i++) {
- const auto node_id = static_cast<s32>(i);
-
- // If we don't have a state, send to our index stack for work
- if (GetState(i) == NodeStates::State::NoState) {
- index_stack.push(node_id);
- }
-
- // While we have work to do in our stack
- while (index_stack.Count() > 0) {
- // Get the current node
- const auto current_stack_index = index_stack.top();
- // Check if we've seen the node yet
- const auto index_state = GetState(current_stack_index);
- if (index_state == NodeStates::State::NoState) {
- // Mark the node as seen
- UpdateState(NodeStates::State::InFound, current_stack_index);
- } else if (index_state == NodeStates::State::InFound) {
- // We've seen this node before, mark it as completed
- UpdateState(NodeStates::State::InCompleted, current_stack_index);
- // Update our index list
- PushTsortResult(current_stack_index);
- // Pop the stack
- index_stack.pop();
- continue;
- } else if (index_state == NodeStates::State::InCompleted) {
- // If our node is already sorted, clear it
- index_stack.pop();
- continue;
- }
-
- const auto edge_node_count = edge_matrix.GetNodeCount();
- for (s32 j = 0; j < static_cast<s32>(edge_node_count); j++) {
- // Check if our node is connected to our edge matrix
- if (!edge_matrix.Connected(current_stack_index, j)) {
- continue;
- }
-
- // Check if our node exists
- const auto node_state = GetState(j);
- if (node_state == NodeStates::State::NoState) {
- // Add more work
- index_stack.push(j);
- } else if (node_state == NodeStates::State::InFound) {
- UNREACHABLE_MSG("Node start marked as found");
- ResetState();
- return false;
- }
- }
- }
- }
- return true;
-}
-
-void NodeStates::ResetState() {
- // Reset to the start of our index stack
- index_pos = 0;
- for (std::size_t i = 0; i < node_count; i++) {
- // Mark all nodes as not found
- was_node_found[i] = false;
- // Mark all nodes as uncompleted
- was_node_completed[i] = false;
- // Mark all indexes as invalid
- index_list[i] = -1;
- }
-}
-
-void NodeStates::UpdateState(NodeStates::State state, std::size_t i) {
- switch (state) {
- case NodeStates::State::NoState:
- was_node_found[i] = false;
- was_node_completed[i] = false;
- break;
- case NodeStates::State::InFound:
- was_node_found[i] = true;
- was_node_completed[i] = false;
- break;
- case NodeStates::State::InCompleted:
- was_node_found[i] = false;
- was_node_completed[i] = true;
- break;
- }
-}
-
-NodeStates::State NodeStates::GetState(std::size_t i) {
- ASSERT(i < node_count);
- if (was_node_found[i]) {
- // If our node exists in our found list
- return NodeStates::State::InFound;
- } else if (was_node_completed[i]) {
- // If node is in the completed list
- return NodeStates::State::InCompleted;
- } else {
- // If in neither
- return NodeStates::State::NoState;
- }
-}
-
-NodeStates::Stack::Stack() = default;
-NodeStates::Stack::~Stack() = default;
-
-void NodeStates::Stack::Reset(std::size_t size) {
- // Mark our stack as empty
- stack.resize(size);
- stack_size = size;
- stack_pos = 0;
- std::fill(stack.begin(), stack.end(), 0);
-}
-
-void NodeStates::Stack::push(s32 val) {
- ASSERT(stack_pos < stack_size);
- stack[stack_pos++] = val;
-}
-
-std::size_t NodeStates::Stack::Count() const {
- return stack_pos;
-}
-
-s32 NodeStates::Stack::top() const {
- ASSERT(stack_pos > 0);
- return stack[stack_pos - 1];
-}
-
-s32 NodeStates::Stack::pop() {
- ASSERT(stack_pos > 0);
- stack_pos--;
- return stack[stack_pos];
-}
-
-EdgeMatrix::EdgeMatrix() = default;
-EdgeMatrix::~EdgeMatrix() = default;
-
-void EdgeMatrix::Initialize(std::size_t _node_count) {
- node_count = _node_count;
- edge_matrix.resize(node_count * node_count);
-}
-
-bool EdgeMatrix::Connected(s32 a, s32 b) {
- return GetState(a, b);
-}
-
-void EdgeMatrix::Connect(s32 a, s32 b) {
- SetState(a, b, true);
-}
-
-void EdgeMatrix::Disconnect(s32 a, s32 b) {
- SetState(a, b, false);
-}
-
-void EdgeMatrix::RemoveEdges(s32 edge) {
- for (std::size_t i = 0; i < node_count; i++) {
- SetState(edge, static_cast<s32>(i), false);
- }
-}
-
-std::size_t EdgeMatrix::GetNodeCount() const {
- return node_count;
-}
-
-void EdgeMatrix::SetState(s32 a, s32 b, bool state) {
- ASSERT(InRange(a, b));
- edge_matrix.at(a * node_count + b) = state;
-}
-
-bool EdgeMatrix::GetState(s32 a, s32 b) {
- ASSERT(InRange(a, b));
- return edge_matrix.at(a * node_count + b);
-}
-
-bool EdgeMatrix::InRange(s32 a, s32 b) const {
- const std::size_t pos = a * node_count + b;
- return pos < (node_count * node_count);
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/splitter_context.h b/src/audio_core/splitter_context.h
deleted file mode 100644
index b490627f5..000000000
--- a/src/audio_core/splitter_context.h
+++ /dev/null
@@ -1,219 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <stack>
-#include <vector>
-#include "audio_core/common.h"
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/swap.h"
-
-namespace AudioCore {
-class BehaviorInfo;
-
-class EdgeMatrix {
-public:
- EdgeMatrix();
- ~EdgeMatrix();
-
- void Initialize(std::size_t _node_count);
- bool Connected(s32 a, s32 b);
- void Connect(s32 a, s32 b);
- void Disconnect(s32 a, s32 b);
- void RemoveEdges(s32 edge);
- std::size_t GetNodeCount() const;
-
-private:
- void SetState(s32 a, s32 b, bool state);
- bool GetState(s32 a, s32 b);
-
- bool InRange(s32 a, s32 b) const;
- std::vector<bool> edge_matrix{};
- std::size_t node_count{};
-};
-
-class NodeStates {
-public:
- enum class State {
- NoState = 0,
- InFound = 1,
- InCompleted = 2,
- };
-
- // Looks to be a fixed size stack. Placed within the NodeStates class based on symbols
- class Stack {
- public:
- Stack();
- ~Stack();
-
- void Reset(std::size_t size);
- void push(s32 val);
- std::size_t Count() const;
- s32 top() const;
- s32 pop();
-
- private:
- std::vector<s32> stack{};
- std::size_t stack_size{};
- std::size_t stack_pos{};
- };
- NodeStates();
- ~NodeStates();
-
- void Initialize(std::size_t node_count_);
- bool Tsort(EdgeMatrix& edge_matrix);
- std::size_t GetIndexPos() const;
- const std::vector<s32>& GetIndexList() const;
-
-private:
- void PushTsortResult(s32 index);
- bool DepthFirstSearch(EdgeMatrix& edge_matrix);
- void ResetState();
- void UpdateState(State state, std::size_t i);
- State GetState(std::size_t i);
-
- std::size_t node_count{};
- std::vector<bool> was_node_found{};
- std::vector<bool> was_node_completed{};
- std::size_t index_pos{};
- std::vector<s32> index_list{};
- Stack index_stack{};
-};
-
-enum class SplitterMagic : u32_le {
- SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'),
- DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'),
- InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'),
-};
-
-class SplitterInfo {
-public:
- struct InHeader {
- SplitterMagic magic{};
- s32_le info_count{};
- s32_le data_count{};
- INSERT_PADDING_WORDS(5);
- };
- static_assert(sizeof(InHeader) == 0x20, "SplitterInfo::InHeader is an invalid size");
-
- struct InInfoPrams {
- SplitterMagic magic{};
- s32_le send_id{};
- s32_le sample_rate{};
- s32_le length{};
- s32_le resource_id_base{};
- };
- static_assert(sizeof(InInfoPrams) == 0x14, "SplitterInfo::InInfoPrams is an invalid size");
-
- struct InDestinationParams {
- SplitterMagic magic{};
- s32_le splitter_id{};
- std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volumes{};
- s32_le mix_id{};
- bool in_use{};
- INSERT_PADDING_BYTES(3);
- };
- static_assert(sizeof(InDestinationParams) == 0x70,
- "SplitterInfo::InDestinationParams is an invalid size");
-};
-
-class ServerSplitterDestinationData {
-public:
- explicit ServerSplitterDestinationData(s32 id_);
- ~ServerSplitterDestinationData();
-
- void Update(SplitterInfo::InDestinationParams& header);
-
- ServerSplitterDestinationData* GetNextDestination();
- const ServerSplitterDestinationData* GetNextDestination() const;
- void SetNextDestination(ServerSplitterDestinationData* dest);
- bool ValidMixId() const;
- s32 GetMixId() const;
- bool IsConfigured() const;
- float GetMixVolume(std::size_t i) const;
- const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& CurrentMixVolumes() const;
- const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& LastMixVolumes() const;
- void MarkDirty();
- void UpdateInternalState();
-
-private:
- bool needs_update{};
- bool in_use{};
- s32 id{};
- s32 mix_id{};
- std::array<float, AudioCommon::MAX_MIX_BUFFERS> current_mix_volumes{};
- std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volumes{};
- ServerSplitterDestinationData* next = nullptr;
-};
-
-class ServerSplitterInfo {
-public:
- explicit ServerSplitterInfo(s32 id_);
- ~ServerSplitterInfo();
-
- void InitializeInfos();
- void ClearNewConnectionFlag();
- std::size_t Update(SplitterInfo::InInfoPrams& header);
-
- ServerSplitterDestinationData* GetHead();
- const ServerSplitterDestinationData* GetHead() const;
- ServerSplitterDestinationData* GetData(std::size_t depth);
- const ServerSplitterDestinationData* GetData(std::size_t depth) const;
-
- bool HasNewConnection() const;
- s32 GetLength() const;
-
- void SetHead(ServerSplitterDestinationData* new_head);
- void SetHeadDepth(s32 length);
-
-private:
- s32 sample_rate{};
- s32 id{};
- s32 send_length{};
- ServerSplitterDestinationData* head = nullptr;
- bool new_connection{};
-};
-
-class SplitterContext {
-public:
- SplitterContext();
- ~SplitterContext();
-
- void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count,
- std::size_t data_count);
-
- bool Update(const std::vector<u8>& input, std::size_t& input_offset, std::size_t& bytes_read);
- bool UsingSplitter() const;
-
- ServerSplitterInfo& GetInfo(std::size_t i);
- const ServerSplitterInfo& GetInfo(std::size_t i) const;
- ServerSplitterDestinationData& GetData(std::size_t i);
- const ServerSplitterDestinationData& GetData(std::size_t i) const;
- ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data);
- const ServerSplitterDestinationData* GetDestinationData(std::size_t info,
- std::size_t data) const;
- void UpdateInternalState();
-
- std::size_t GetInfoCount() const;
- std::size_t GetDataCount() const;
-
-private:
- void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed);
- bool UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
- std::size_t& bytes_read, s32 in_splitter_count);
- bool UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
- std::size_t& bytes_read, s32 in_data_count);
- bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header,
- const std::vector<u8>& input, const std::size_t& input_offset);
-
- std::vector<ServerSplitterInfo> infos{};
- std::vector<ServerSplitterDestinationData> datas{};
-
- std::size_t info_count{};
- std::size_t data_count{};
- bool bug_fixed{};
-};
-} // namespace AudioCore
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
deleted file mode 100644
index 0abc989b2..000000000
--- a/src/audio_core/stream.cpp
+++ /dev/null
@@ -1,175 +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 "audio_core/sink.h"
-#include "audio_core/sink_details.h"
-#include "audio_core/sink_stream.h"
-#include "audio_core/stream.h"
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "common/settings.h"
-#include "core/core_timing.h"
-
-namespace AudioCore {
-
-constexpr std::size_t MaxAudioBufferCount{32};
-
-u32 Stream::GetNumChannels() const {
- switch (format) {
- case Format::Mono16:
- return 1;
- case Format::Stereo16:
- return 2;
- case Format::Multi51Channel16:
- return 6;
- }
- UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format));
- return {};
-}
-
-Stream::Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_,
- ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_)
- : sample_rate{sample_rate_}, format{format_}, release_callback{std::move(release_callback_)},
- sink_stream{sink_stream_}, core_timing{core_timing_}, name{std::move(name_)} {
- release_event =
- Core::Timing::CreateEvent(name, [this](std::uintptr_t, std::chrono::nanoseconds ns_late) {
- ReleaseActiveBuffer(ns_late);
- });
-}
-
-void Stream::Play() {
- state = State::Playing;
- PlayNextBuffer();
-}
-
-void Stream::Stop() {
- state = State::Stopped;
- UNIMPLEMENTED();
-}
-
-bool Stream::Flush() {
- const bool had_buffers = !queued_buffers.empty();
- while (!queued_buffers.empty()) {
- queued_buffers.pop();
- }
- return had_buffers;
-}
-
-void Stream::SetVolume(float volume) {
- game_volume = volume;
-}
-
-Stream::State Stream::GetState() const {
- return state;
-}
-
-std::chrono::nanoseconds Stream::GetBufferReleaseNS(const Buffer& buffer) const {
- const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
- return std::chrono::nanoseconds((static_cast<u64>(num_samples) * 1000000000ULL) / sample_rate);
-}
-
-static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) {
- const float volume{std::clamp(Settings::Volume() - (1.0f - game_volume), 0.0f, 1.0f)};
-
- if (volume == 1.0f) {
- return;
- }
-
- // Perceived volume is not the same as the volume level
- const float volume_scale_factor = (0.85f * ((volume * volume) - volume)) + volume;
- for (auto& sample : samples) {
- sample = static_cast<s16>(sample * volume_scale_factor);
- }
-}
-
-void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) {
- if (!IsPlaying()) {
- // Ensure we are in playing state before playing the next buffer
- sink_stream.Flush();
- return;
- }
-
- if (active_buffer) {
- // Do not queue a new buffer if we are already playing a buffer
- return;
- }
-
- if (queued_buffers.empty()) {
- // No queued buffers - we are effectively paused
- sink_stream.Flush();
- return;
- }
-
- active_buffer = queued_buffers.front();
- queued_buffers.pop();
-
- auto& samples = active_buffer->GetSamples();
-
- VolumeAdjustSamples(samples, game_volume);
-
- sink_stream.EnqueueSamples(GetNumChannels(), samples);
- played_samples += samples.size();
-
- const auto buffer_release_ns = GetBufferReleaseNS(*active_buffer);
-
- // If ns_late is higher than the update rate ignore the delay
- if (ns_late > buffer_release_ns) {
- ns_late = {};
- }
-
- core_timing.ScheduleEvent(buffer_release_ns - ns_late, release_event, {});
-}
-
-void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) {
- ASSERT(active_buffer);
- released_buffers.push(std::move(active_buffer));
- release_callback();
- PlayNextBuffer(ns_late);
-}
-
-bool Stream::QueueBuffer(BufferPtr&& buffer) {
- if (queued_buffers.size() < MaxAudioBufferCount) {
- queued_buffers.push(std::move(buffer));
- PlayNextBuffer();
- return true;
- }
- return false;
-}
-
-bool Stream::ContainsBuffer([[maybe_unused]] Buffer::Tag tag) const {
- UNIMPLEMENTED();
- return {};
-}
-
-std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers(std::size_t max_count) {
- std::vector<Buffer::Tag> tags;
- for (std::size_t count = 0; count < max_count && !released_buffers.empty(); ++count) {
- if (released_buffers.front()) {
- tags.push_back(released_buffers.front()->GetTag());
- } else {
- ASSERT_MSG(false, "Invalid tag in released_buffers!");
- }
- released_buffers.pop();
- }
- return tags;
-}
-
-std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers() {
- std::vector<Buffer::Tag> tags;
- tags.reserve(released_buffers.size());
- while (!released_buffers.empty()) {
- if (released_buffers.front()) {
- tags.push_back(released_buffers.front()->GetTag());
- } else {
- ASSERT_MSG(false, "Invalid tag in released_buffers!");
- }
- released_buffers.pop();
- }
- return tags;
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h
deleted file mode 100644
index dbd97ec9c..000000000
--- a/src/audio_core/stream.h
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <chrono>
-#include <functional>
-#include <memory>
-#include <string>
-#include <vector>
-#include <queue>
-
-#include "audio_core/buffer.h"
-#include "common/common_types.h"
-
-namespace Core::Timing {
-class CoreTiming;
-struct EventType;
-} // namespace Core::Timing
-
-namespace AudioCore {
-
-class SinkStream;
-
-/**
- * Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut
- */
-class Stream {
-public:
- /// Audio format of the stream
- enum class Format {
- Mono16,
- Stereo16,
- Multi51Channel16,
- };
-
- /// Current state of the stream
- enum class State {
- Stopped,
- Playing,
- };
-
- /// Callback function type, used to change guest state on a buffer being released
- using ReleaseCallback = std::function<void()>;
-
- Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_,
- ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_);
-
- /// Plays the audio stream
- void Play();
-
- /// Stops the audio stream
- void Stop();
-
- /// Queues a buffer into the audio stream, returns true on success
- bool QueueBuffer(BufferPtr&& buffer);
-
- /// Flush audio buffers
- bool Flush();
-
- /// Returns true if the audio stream contains a buffer with the specified tag
- [[nodiscard]] bool ContainsBuffer(Buffer::Tag tag) const;
-
- /// Returns a vector of recently released buffers specified by tag
- [[nodiscard]] std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(std::size_t max_count);
-
- /// Returns a vector of all recently released buffers specified by tag
- [[nodiscard]] std::vector<Buffer::Tag> GetTagsAndReleaseBuffers();
-
- void SetVolume(float volume);
-
- [[nodiscard]] float GetVolume() const {
- return game_volume;
- }
-
- /// Returns true if the stream is currently playing
- [[nodiscard]] bool IsPlaying() const {
- return state == State::Playing;
- }
-
- /// Returns the number of queued buffers
- [[nodiscard]] std::size_t GetQueueSize() const {
- return queued_buffers.size();
- }
-
- /// Gets the sample rate
- [[nodiscard]] u32 GetSampleRate() const {
- return sample_rate;
- }
-
- /// Gets the number of samples played so far
- [[nodiscard]] u64 GetPlayedSampleCount() const {
- return played_samples;
- }
-
- /// Gets the number of channels
- [[nodiscard]] u32 GetNumChannels() const;
-
- /// Get the state
- [[nodiscard]] State GetState() const;
-
-private:
- /// Plays the next queued buffer in the audio stream, starting playback if necessary
- void PlayNextBuffer(std::chrono::nanoseconds ns_late = {});
-
- /// Releases the actively playing buffer, signalling that it has been completed
- void ReleaseActiveBuffer(std::chrono::nanoseconds ns_late = {});
-
- /// Gets the number of core cycles when the specified buffer will be released
- [[nodiscard]] std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const;
-
- u32 sample_rate; ///< Sample rate of the stream
- u64 played_samples{}; ///< The current played sample count
- Format format; ///< Format of the stream
- float game_volume = 1.0f; ///< The volume the game currently has set
- ReleaseCallback release_callback; ///< Buffer release callback for the stream
- State state{State::Stopped}; ///< Playback state of the stream
- std::shared_ptr<Core::Timing::EventType>
- release_event; ///< Core timing release event for the stream
- BufferPtr active_buffer; ///< Actively playing buffer in the stream
- std::queue<BufferPtr> queued_buffers; ///< Buffers queued to be played in the stream
- std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream
- SinkStream& sink_stream; ///< Output sink for the stream
- Core::Timing::CoreTiming& core_timing; ///< Core timing instance.
- std::string name; ///< Name of the stream, must be unique
-};
-
-using StreamPtr = std::shared_ptr<Stream>;
-
-} // namespace AudioCore
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/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp
deleted file mode 100644
index 75012a887..000000000
--- a/src/audio_core/voice_context.cpp
+++ /dev/null
@@ -1,580 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-
-#include "audio_core/behavior_info.h"
-#include "audio_core/voice_context.h"
-#include "core/memory.h"
-
-namespace AudioCore {
-
-ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id_) : id(id_) {}
-ServerVoiceChannelResource::~ServerVoiceChannelResource() = default;
-
-bool ServerVoiceChannelResource::InUse() const {
- return in_use;
-}
-
-float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const {
- ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
- return mix_volume.at(i);
-}
-
-float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const {
- ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
- return last_mix_volume.at(i);
-}
-
-void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) {
- in_use = in_params.in_use;
- // Update our mix volumes only if it's in use
- if (in_params.in_use) {
- mix_volume = in_params.mix_volume;
- }
-}
-
-void ServerVoiceChannelResource::UpdateLastMixVolumes() {
- last_mix_volume = mix_volume;
-}
-
-const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
-ServerVoiceChannelResource::GetCurrentMixVolume() const {
- return mix_volume;
-}
-
-const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
-ServerVoiceChannelResource::GetLastMixVolume() const {
- return last_mix_volume;
-}
-
-ServerVoiceInfo::ServerVoiceInfo() {
- Initialize();
-}
-ServerVoiceInfo::~ServerVoiceInfo() = default;
-
-void ServerVoiceInfo::Initialize() {
- in_params.in_use = false;
- in_params.node_id = 0;
- in_params.id = 0;
- in_params.current_playstate = ServerPlayState::Stop;
- in_params.priority = 255;
- in_params.sample_rate = 0;
- in_params.sample_format = SampleFormat::Invalid;
- in_params.channel_count = 0;
- in_params.pitch = 0.0f;
- in_params.volume = 0.0f;
- in_params.last_volume = 0.0f;
- in_params.biquad_filter.fill({});
- in_params.wave_buffer_count = 0;
- in_params.wave_buffer_head = 0;
- in_params.mix_id = AudioCommon::NO_MIX;
- in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
- in_params.additional_params_address = 0;
- in_params.additional_params_size = 0;
- in_params.is_new = false;
- out_params.played_sample_count = 0;
- out_params.wave_buffer_consumed = 0;
- in_params.voice_drop_flag = false;
- in_params.buffer_mapped = true;
- in_params.wave_buffer_flush_request_count = 0;
- in_params.was_biquad_filter_enabled.fill(false);
-
- for (auto& wave_buffer : in_params.wave_buffer) {
- wave_buffer.start_sample_offset = 0;
- wave_buffer.end_sample_offset = 0;
- wave_buffer.is_looping = false;
- wave_buffer.end_of_stream = false;
- wave_buffer.buffer_address = 0;
- wave_buffer.buffer_size = 0;
- wave_buffer.context_address = 0;
- wave_buffer.context_size = 0;
- wave_buffer.sent_to_dsp = true;
- }
-
- stored_samples.clear();
-}
-
-void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in,
- BehaviorInfo& behavior_info) {
- in_params.in_use = voice_in.is_in_use;
- in_params.id = voice_in.id;
- in_params.node_id = voice_in.node_id;
- in_params.last_playstate = in_params.current_playstate;
- switch (voice_in.play_state) {
- case PlayState::Paused:
- in_params.current_playstate = ServerPlayState::Paused;
- break;
- case PlayState::Stopped:
- if (in_params.current_playstate != ServerPlayState::Stop) {
- in_params.current_playstate = ServerPlayState::RequestStop;
- }
- break;
- case PlayState::Started:
- in_params.current_playstate = ServerPlayState::Play;
- break;
- default:
- UNREACHABLE_MSG("Unknown playstate {}", voice_in.play_state);
- break;
- }
-
- in_params.priority = voice_in.priority;
- in_params.sorting_order = voice_in.sorting_order;
- in_params.sample_rate = voice_in.sample_rate;
- in_params.sample_format = voice_in.sample_format;
- in_params.channel_count = voice_in.channel_count;
- in_params.pitch = voice_in.pitch;
- in_params.volume = voice_in.volume;
- in_params.biquad_filter = voice_in.biquad_filter;
- in_params.wave_buffer_count = voice_in.wave_buffer_count;
- in_params.wave_buffer_head = voice_in.wave_buffer_head;
- if (behavior_info.IsFlushVoiceWaveBuffersSupported()) {
- const auto in_request_count = in_params.wave_buffer_flush_request_count;
- const auto voice_request_count = voice_in.wave_buffer_flush_request_count;
- in_params.wave_buffer_flush_request_count =
- static_cast<u8>(in_request_count + voice_request_count);
- }
- in_params.mix_id = voice_in.mix_id;
- if (behavior_info.IsSplitterSupported()) {
- in_params.splitter_info_id = voice_in.splitter_info_id;
- } else {
- in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
- }
-
- std::memcpy(in_params.voice_channel_resource_id.data(),
- voice_in.voice_channel_resource_ids.data(),
- sizeof(s32) * in_params.voice_channel_resource_id.size());
-
- if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
- in_params.behavior_flags.is_played_samples_reset_at_loop_point =
- voice_in.behavior_flags.is_played_samples_reset_at_loop_point;
- } else {
- in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0);
- }
- if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) {
- in_params.behavior_flags.is_pitch_and_src_skipped =
- voice_in.behavior_flags.is_pitch_and_src_skipped;
- } else {
- in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0);
- }
-
- if (voice_in.is_voice_drop_flag_clear_requested) {
- in_params.voice_drop_flag = false;
- }
-
- if (in_params.additional_params_address != voice_in.additional_params_address ||
- in_params.additional_params_size != voice_in.additional_params_size) {
- in_params.additional_params_address = voice_in.additional_params_address;
- in_params.additional_params_size = voice_in.additional_params_size;
- // TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that
- // our context is new
- }
-}
-
-void ServerVoiceInfo::UpdateWaveBuffers(
- const VoiceInfo::InParams& voice_in,
- std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
- BehaviorInfo& behavior_info) {
- if (voice_in.is_new) {
- // Initialize our wave buffers
- for (auto& wave_buffer : in_params.wave_buffer) {
- wave_buffer.start_sample_offset = 0;
- wave_buffer.end_sample_offset = 0;
- wave_buffer.is_looping = false;
- wave_buffer.end_of_stream = false;
- wave_buffer.buffer_address = 0;
- wave_buffer.buffer_size = 0;
- wave_buffer.context_address = 0;
- wave_buffer.context_size = 0;
- wave_buffer.loop_start_sample = 0;
- wave_buffer.loop_end_sample = 0;
- wave_buffer.sent_to_dsp = true;
- }
-
- // Mark all our wave buffers as invalid
- for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count);
- channel++) {
- for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; ++i) {
- voice_states[channel]->is_wave_buffer_valid[i] = false;
- }
- }
- }
-
- // Update our wave buffers
- for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
- // Assume that we have at least 1 channel voice state
- const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i];
-
- UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format,
- have_valid_wave_buffer, behavior_info);
- }
-}
-
-void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
- const WaveBuffer& in_wave_buffer, SampleFormat sample_format,
- bool is_buffer_valid,
- [[maybe_unused]] BehaviorInfo& behavior_info) {
- if (!is_buffer_valid && out_wavebuffer.sent_to_dsp && out_wavebuffer.buffer_address != 0) {
- out_wavebuffer.buffer_address = 0;
- out_wavebuffer.buffer_size = 0;
- }
-
- if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) {
- // Validate sample offset sizings
- if (sample_format == SampleFormat::Pcm16) {
- const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size);
- const s64 start = sizeof(s16) * in_wave_buffer.start_sample_offset;
- const s64 end = sizeof(s16) * in_wave_buffer.end_sample_offset;
- if (0 > start || start > buffer_size || 0 > end || end > buffer_size) {
- // TODO(ogniK): Write error info
- LOG_ERROR(Audio,
- "PCM16 wavebuffer has an invalid size. Buffer has size 0x{:08X}, but "
- "offsets were "
- "{:08X} - 0x{:08X}",
- buffer_size, sizeof(s16) * in_wave_buffer.start_sample_offset,
- sizeof(s16) * in_wave_buffer.end_sample_offset);
- return;
- }
- } else if (sample_format == SampleFormat::Adpcm) {
- const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size);
- const s64 start_frames = in_wave_buffer.start_sample_offset / 14;
- const s64 start_extra = in_wave_buffer.start_sample_offset % 14 == 0
- ? 0
- : (in_wave_buffer.start_sample_offset % 14) / 2 + 1 +
- (in_wave_buffer.start_sample_offset % 2);
- const s64 start = start_frames * 8 + start_extra;
- const s64 end_frames = in_wave_buffer.end_sample_offset / 14;
- const s64 end_extra = in_wave_buffer.end_sample_offset % 14 == 0
- ? 0
- : (in_wave_buffer.end_sample_offset % 14) / 2 + 1 +
- (in_wave_buffer.end_sample_offset % 2);
- const s64 end = end_frames * 8 + end_extra;
- if (in_wave_buffer.start_sample_offset < 0 || start > buffer_size ||
- in_wave_buffer.end_sample_offset < 0 || end > buffer_size) {
- LOG_ERROR(Audio,
- "ADPMC wavebuffer has an invalid size. Buffer has size 0x{:08X}, but "
- "offsets were "
- "{:08X} - 0x{:08X}",
- in_wave_buffer.buffer_size, start, end);
- return;
- }
- }
- // TODO(ogniK): ADPCM Size error
-
- out_wavebuffer.sent_to_dsp = false;
- out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset;
- out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset;
- out_wavebuffer.is_looping = in_wave_buffer.is_looping;
- out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream;
-
- out_wavebuffer.buffer_address = in_wave_buffer.buffer_address;
- out_wavebuffer.buffer_size = in_wave_buffer.buffer_size;
- out_wavebuffer.context_address = in_wave_buffer.context_address;
- out_wavebuffer.context_size = in_wave_buffer.context_size;
- out_wavebuffer.loop_start_sample = in_wave_buffer.loop_start_sample;
- out_wavebuffer.loop_end_sample = in_wave_buffer.loop_end_sample;
- in_params.buffer_mapped =
- in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0;
- // TODO(ogniK): Pool mapper attachment
- // TODO(ogniK): IsAdpcmLoopContextBugFixed
- if (sample_format == SampleFormat::Adpcm && in_wave_buffer.context_address != 0 &&
- in_wave_buffer.context_size != 0 && behavior_info.IsAdpcmLoopContextBugFixed()) {
- } else {
- out_wavebuffer.context_address = 0;
- out_wavebuffer.context_size = 0;
- }
- }
-}
-
-void ServerVoiceInfo::WriteOutStatus(
- VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
- std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) {
- if (voice_in.is_new || in_params.is_new) {
- in_params.is_new = true;
- voice_out.wave_buffer_consumed = 0;
- voice_out.played_sample_count = 0;
- voice_out.voice_dropped = false;
- } else {
- const auto& state = voice_states[0];
- voice_out.wave_buffer_consumed = state->wave_buffer_consumed;
- voice_out.played_sample_count = state->played_sample_count;
- voice_out.voice_dropped = state->voice_dropped;
- }
-}
-
-const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const {
- return in_params;
-}
-
-ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() {
- return in_params;
-}
-
-const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const {
- return out_params;
-}
-
-ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() {
- return out_params;
-}
-
-bool ServerVoiceInfo::ShouldSkip() const {
- // TODO(ogniK): Handle unmapped wave buffers or parameters
- return !in_params.in_use || in_params.wave_buffer_count == 0 || !in_params.buffer_mapped ||
- in_params.voice_drop_flag;
-}
-
-bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
- std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> dsp_voice_states{};
- if (in_params.is_new) {
- ResetResources(voice_context);
- in_params.last_volume = in_params.volume;
- in_params.is_new = false;
- }
-
- const s32 channel_count = in_params.channel_count;
- for (s32 i = 0; i < channel_count; i++) {
- const auto channel_resource = in_params.voice_channel_resource_id[i];
- dsp_voice_states[i] =
- &voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
- }
- return UpdateParametersForCommandGeneration(dsp_voice_states);
-}
-
-void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) {
- const s32 channel_count = in_params.channel_count;
- for (s32 i = 0; i < channel_count; i++) {
- const auto channel_resource = in_params.voice_channel_resource_id[i];
- auto& dsp_state =
- voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
- dsp_state = {};
- voice_context.GetChannelResource(static_cast<std::size_t>(channel_resource))
- .UpdateLastMixVolumes();
- }
-}
-
-bool ServerVoiceInfo::UpdateParametersForCommandGeneration(
- std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states) {
- const s32 channel_count = in_params.channel_count;
- if (in_params.wave_buffer_flush_request_count > 0) {
- FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states,
- channel_count);
- in_params.wave_buffer_flush_request_count = 0;
- }
-
- switch (in_params.current_playstate) {
- case ServerPlayState::Play: {
- for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
- if (!in_params.wave_buffer[i].sent_to_dsp) {
- for (s32 channel = 0; channel < channel_count; channel++) {
- dsp_voice_states[channel]->is_wave_buffer_valid[i] = true;
- }
- in_params.wave_buffer[i].sent_to_dsp = true;
- }
- }
- in_params.should_depop = false;
- return HasValidWaveBuffer(dsp_voice_states[0]);
- }
- case ServerPlayState::Paused:
- case ServerPlayState::Stop: {
- in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
- return in_params.should_depop;
- }
- case ServerPlayState::RequestStop: {
- for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
- in_params.wave_buffer[i].sent_to_dsp = true;
- for (s32 channel = 0; channel < channel_count; channel++) {
- auto* dsp_state = dsp_voice_states[channel];
-
- if (dsp_state->is_wave_buffer_valid[i]) {
- dsp_state->wave_buffer_index =
- (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
- dsp_state->wave_buffer_consumed++;
- }
-
- dsp_state->is_wave_buffer_valid[i] = false;
- }
- }
-
- for (s32 channel = 0; channel < channel_count; channel++) {
- auto* dsp_state = dsp_voice_states[channel];
- dsp_state->offset = 0;
- dsp_state->played_sample_count = 0;
- dsp_state->fraction = 0;
- dsp_state->sample_history.fill(0);
- dsp_state->context = {};
- }
-
- in_params.current_playstate = ServerPlayState::Stop;
- in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
- return in_params.should_depop;
- }
- default:
- UNREACHABLE_MSG("Invalid playstate {}", in_params.current_playstate);
- }
-
- return false;
-}
-
-void ServerVoiceInfo::FlushWaveBuffers(
- u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
- s32 channel_count) {
- auto wave_head = in_params.wave_buffer_head;
-
- for (u8 i = 0; i < flush_count; i++) {
- in_params.wave_buffer[wave_head].sent_to_dsp = true;
- for (s32 channel = 0; channel < channel_count; channel++) {
- auto* dsp_state = dsp_voice_states[channel];
- dsp_state->wave_buffer_consumed++;
- dsp_state->is_wave_buffer_valid[wave_head] = false;
- dsp_state->wave_buffer_index =
- (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
- }
- wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS;
- }
-}
-
-bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const {
- const auto& valid_wb = state->is_wave_buffer_valid;
- return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end();
-}
-
-void ServerVoiceInfo::SetWaveBufferCompleted(VoiceState& dsp_state,
- const ServerWaveBuffer& wave_buffer) {
- dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
- dsp_state.wave_buffer_consumed++;
- dsp_state.wave_buffer_index = (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
- dsp_state.loop_count = 0;
- if (wave_buffer.end_of_stream) {
- dsp_state.played_sample_count = 0;
- }
-}
-
-VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} {
- for (std::size_t i = 0; i < voice_count; i++) {
- voice_channel_resources.emplace_back(static_cast<s32>(i));
- sorted_voice_info.push_back(&voice_info.emplace_back());
- voice_states.emplace_back();
- dsp_voice_states.emplace_back();
- }
-}
-
-VoiceContext::~VoiceContext() {
- sorted_voice_info.clear();
-}
-
-std::size_t VoiceContext::GetVoiceCount() const {
- return voice_count;
-}
-
-ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) {
- ASSERT(i < voice_count);
- return voice_channel_resources.at(i);
-}
-
-const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const {
- ASSERT(i < voice_count);
- return voice_channel_resources.at(i);
-}
-
-VoiceState& VoiceContext::GetState(std::size_t i) {
- ASSERT(i < voice_count);
- return voice_states.at(i);
-}
-
-const VoiceState& VoiceContext::GetState(std::size_t i) const {
- ASSERT(i < voice_count);
- return voice_states.at(i);
-}
-
-VoiceState& VoiceContext::GetDspSharedState(std::size_t i) {
- ASSERT(i < voice_count);
- return dsp_voice_states.at(i);
-}
-
-const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const {
- ASSERT(i < voice_count);
- return dsp_voice_states.at(i);
-}
-
-ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) {
- ASSERT(i < voice_count);
- return voice_info.at(i);
-}
-
-const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const {
- ASSERT(i < voice_count);
- return voice_info.at(i);
-}
-
-ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) {
- ASSERT(i < voice_count);
- return *sorted_voice_info.at(i);
-}
-
-const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const {
- ASSERT(i < voice_count);
- return *sorted_voice_info.at(i);
-}
-
-s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
- s32 channel_count, s32 buffer_offset, s32 sample_count,
- Core::Memory::Memory& memory) {
- if (wave_buffer->buffer_address == 0) {
- return 0;
- }
- if (wave_buffer->buffer_size == 0) {
- return 0;
- }
- if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) {
- return 0;
- }
-
- const auto samples_remaining =
- (wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset;
- const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count;
- const auto buffer_pos = wave_buffer->buffer_address + start_offset;
-
- s16* buffer_data = reinterpret_cast<s16*>(memory.GetPointer(buffer_pos));
-
- const auto samples_processed = std::min(sample_count, samples_remaining);
-
- // Fast path
- if (channel_count == 1) {
- for (std::ptrdiff_t i = 0; i < samples_processed; i++) {
- output_buffer[i] = buffer_data[i];
- }
- } else {
- for (std::ptrdiff_t i = 0; i < samples_processed; i++) {
- output_buffer[i] = buffer_data[i * channel_count + channel];
- }
- }
-
- return samples_processed;
-}
-
-void VoiceContext::SortInfo() {
- for (std::size_t i = 0; i < voice_count; i++) {
- sorted_voice_info[i] = &voice_info[i];
- }
-
- std::sort(sorted_voice_info.begin(), sorted_voice_info.end(),
- [](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) {
- const auto& lhs_in = lhs->GetInParams();
- const auto& rhs_in = rhs->GetInParams();
- // Sort by priority
- if (lhs_in.priority != rhs_in.priority) {
- return lhs_in.priority > rhs_in.priority;
- } else {
- // If the priorities match, sort by sorting order
- return lhs_in.sorting_order > rhs_in.sorting_order;
- }
- });
-}
-
-void VoiceContext::UpdateStateByDspShared() {
- voice_states = dsp_voice_states;
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h
deleted file mode 100644
index e1050897b..000000000
--- a/src/audio_core/voice_context.h
+++ /dev/null
@@ -1,303 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include "audio_core/algorithm/interpolate.h"
-#include "audio_core/codec.h"
-#include "audio_core/common.h"
-#include "common/bit_field.h"
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-
-namespace Core::Memory {
-class Memory;
-}
-
-namespace AudioCore {
-
-class BehaviorInfo;
-class VoiceContext;
-
-enum class SampleFormat : u8 {
- Invalid = 0,
- Pcm8 = 1,
- Pcm16 = 2,
- Pcm24 = 3,
- Pcm32 = 4,
- PcmFloat = 5,
- Adpcm = 6,
-};
-
-enum class PlayState : u8 {
- Started = 0,
- Stopped = 1,
- Paused = 2,
-};
-
-enum class ServerPlayState {
- Play = 0,
- Stop = 1,
- RequestStop = 2,
- Paused = 3,
-};
-
-struct BiquadFilterParameter {
- bool enabled{};
- INSERT_PADDING_BYTES(1);
- std::array<s16, 3> numerator{};
- std::array<s16, 2> denominator{};
-};
-static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size");
-
-struct WaveBuffer {
- u64_le buffer_address{};
- u64_le buffer_size{};
- s32_le start_sample_offset{};
- s32_le end_sample_offset{};
- u8 is_looping{};
- u8 end_of_stream{};
- u8 sent_to_server{};
- INSERT_PADDING_BYTES(1);
- s32 loop_count{};
- u64 context_address{};
- u64 context_size{};
- u32 loop_start_sample{};
- u32 loop_end_sample{};
-};
-static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size");
-
-struct ServerWaveBuffer {
- VAddr buffer_address{};
- std::size_t buffer_size{};
- s32 start_sample_offset{};
- s32 end_sample_offset{};
- bool is_looping{};
- bool end_of_stream{};
- VAddr context_address{};
- std::size_t context_size{};
- s32 loop_count{};
- u32 loop_start_sample{};
- u32 loop_end_sample{};
- bool sent_to_dsp{true};
-};
-
-struct BehaviorFlags {
- BitField<0, 1, u16> is_played_samples_reset_at_loop_point;
- BitField<1, 1, u16> is_pitch_and_src_skipped;
-};
-static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size");
-
-struct ADPCMContext {
- u16 header;
- s16 yn1;
- s16 yn2;
-};
-static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size");
-
-struct VoiceState {
- s64 played_sample_count;
- s32 offset;
- s32 wave_buffer_index;
- std::array<bool, AudioCommon::MAX_WAVE_BUFFERS> is_wave_buffer_valid;
- s32 wave_buffer_consumed;
- std::array<s32, AudioCommon::MAX_SAMPLE_HISTORY> sample_history;
- s32 fraction;
- VAddr context_address;
- Codec::ADPCM_Coeff coeff;
- ADPCMContext context;
- std::array<s64, 2> biquad_filter_state;
- std::array<s32, AudioCommon::MAX_MIX_BUFFERS> previous_samples;
- u32 external_context_size;
- bool is_external_context_used;
- bool voice_dropped;
- s32 loop_count;
-};
-
-class VoiceChannelResource {
-public:
- struct InParams {
- s32_le id{};
- std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
- bool in_use{};
- INSERT_PADDING_BYTES(11);
- };
- static_assert(sizeof(InParams) == 0x70, "InParams is an invalid size");
-};
-
-class ServerVoiceChannelResource {
-public:
- explicit ServerVoiceChannelResource(s32 id_);
- ~ServerVoiceChannelResource();
-
- bool InUse() const;
- float GetCurrentMixVolumeAt(std::size_t i) const;
- float GetLastMixVolumeAt(std::size_t i) const;
- void Update(VoiceChannelResource::InParams& in_params);
- void UpdateLastMixVolumes();
-
- const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetCurrentMixVolume() const;
- const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetLastMixVolume() const;
-
-private:
- s32 id{};
- std::array<float, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
- std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volume{};
- bool in_use{};
-};
-
-class VoiceInfo {
-public:
- struct InParams {
- s32_le id{};
- u32_le node_id{};
- u8 is_new{};
- u8 is_in_use{};
- PlayState play_state{};
- SampleFormat sample_format{};
- s32_le sample_rate{};
- s32_le priority{};
- s32_le sorting_order{};
- s32_le channel_count{};
- float_le pitch{};
- float_le volume{};
- std::array<BiquadFilterParameter, 2> biquad_filter{};
- s32_le wave_buffer_count{};
- s16_le wave_buffer_head{};
- INSERT_PADDING_BYTES(6);
- u64_le additional_params_address{};
- u64_le additional_params_size{};
- s32_le mix_id{};
- s32_le splitter_info_id{};
- std::array<WaveBuffer, 4> wave_buffer{};
- std::array<u32_le, 6> voice_channel_resource_ids{};
- // TODO(ogniK): Remaining flags
- u8 is_voice_drop_flag_clear_requested{};
- u8 wave_buffer_flush_request_count{};
- INSERT_PADDING_BYTES(2);
- BehaviorFlags behavior_flags{};
- INSERT_PADDING_BYTES(16);
- };
- static_assert(sizeof(InParams) == 0x170, "InParams is an invalid size");
-
- struct OutParams {
- u64_le played_sample_count{};
- u32_le wave_buffer_consumed{};
- u8 voice_dropped{};
- INSERT_PADDING_BYTES(3);
- };
- static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size");
-};
-
-class ServerVoiceInfo {
-public:
- struct InParams {
- bool in_use{};
- bool is_new{};
- bool should_depop{};
- SampleFormat sample_format{};
- s32 sample_rate{};
- s32 channel_count{};
- s32 id{};
- s32 node_id{};
- s32 mix_id{};
- ServerPlayState current_playstate{};
- ServerPlayState last_playstate{};
- s32 priority{};
- s32 sorting_order{};
- float pitch{};
- float volume{};
- float last_volume{};
- std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{};
- s32 wave_buffer_count{};
- s16 wave_buffer_head{};
- INSERT_PADDING_BYTES(2);
- BehaviorFlags behavior_flags{};
- VAddr additional_params_address{};
- std::size_t additional_params_size{};
- std::array<ServerWaveBuffer, AudioCommon::MAX_WAVE_BUFFERS> wave_buffer{};
- std::array<s32, AudioCommon::MAX_CHANNEL_COUNT> voice_channel_resource_id{};
- s32 splitter_info_id{};
- u8 wave_buffer_flush_request_count{};
- bool voice_drop_flag{};
- bool buffer_mapped{};
- std::array<bool, AudioCommon::MAX_BIQUAD_FILTERS> was_biquad_filter_enabled{};
- };
-
- struct OutParams {
- s64 played_sample_count{};
- s32 wave_buffer_consumed{};
- };
-
- ServerVoiceInfo();
- ~ServerVoiceInfo();
- void Initialize();
- void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info);
- void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in,
- std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
- BehaviorInfo& behavior_info);
- void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer,
- SampleFormat sample_format, bool is_buffer_valid,
- BehaviorInfo& behavior_info);
- void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
- std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states);
-
- const InParams& GetInParams() const;
- InParams& GetInParams();
-
- const OutParams& GetOutParams() const;
- OutParams& GetOutParams();
-
- bool ShouldSkip() const;
- bool UpdateForCommandGeneration(VoiceContext& voice_context);
- void ResetResources(VoiceContext& voice_context);
- bool UpdateParametersForCommandGeneration(
- std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states);
- void FlushWaveBuffers(u8 flush_count,
- std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
- s32 channel_count);
- void SetWaveBufferCompleted(VoiceState& dsp_state, const ServerWaveBuffer& wave_buffer);
-
-private:
- std::vector<s16> stored_samples;
- InParams in_params{};
- OutParams out_params{};
-
- bool HasValidWaveBuffer(const VoiceState* state) const;
-};
-
-class VoiceContext {
-public:
- explicit VoiceContext(std::size_t voice_count_);
- ~VoiceContext();
-
- std::size_t GetVoiceCount() const;
- ServerVoiceChannelResource& GetChannelResource(std::size_t i);
- const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const;
- VoiceState& GetState(std::size_t i);
- const VoiceState& GetState(std::size_t i) const;
- VoiceState& GetDspSharedState(std::size_t i);
- const VoiceState& GetDspSharedState(std::size_t i) const;
- ServerVoiceInfo& GetInfo(std::size_t i);
- const ServerVoiceInfo& GetInfo(std::size_t i) const;
- ServerVoiceInfo& GetSortedInfo(std::size_t i);
- const ServerVoiceInfo& GetSortedInfo(std::size_t i) const;
-
- s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
- s32 channel_count, s32 buffer_offset, s32 sample_count,
- Core::Memory::Memory& memory);
- void SortInfo();
- void UpdateStateByDspShared();
-
-private:
- std::size_t voice_count{};
- std::vector<ServerVoiceChannelResource> voice_channel_resources{};
- std::vector<VoiceState> voice_states{};
- std::vector<VoiceState> dsp_voice_states{};
- std::vector<ServerVoiceInfo> voice_info{};
- std::vector<ServerVoiceInfo*> sorted_voice_info{};
-};
-
-} // namespace AudioCore
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index adf70eb8b..a02696873 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
if (DEFINED ENV{AZURECIREPO})
set(BUILD_REPOSITORY $ENV{AZURECIREPO})
endif()
@@ -11,38 +14,17 @@ if (DEFINED ENV{DISPLAYVERSION})
set(DISPLAY_VERSION $ENV{DISPLAYVERSION})
endif ()
-# Pass the path to git to the GenerateSCMRev.cmake as well
-find_package(Git QUIET)
-
-add_custom_command(OUTPUT scm_rev.cpp
- COMMAND ${CMAKE_COMMAND}
- -DSRC_DIR=${CMAKE_SOURCE_DIR}
- -DBUILD_REPOSITORY=${BUILD_REPOSITORY}
- -DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE}
- -DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING}
- -DBUILD_TAG=${BUILD_TAG}
- -DBUILD_ID=${DISPLAY_VERSION}
- -DGIT_REF_SPEC=${GIT_REF_SPEC}
- -DGIT_REV=${GIT_REV}
- -DGIT_DESC=${GIT_DESC}
- -DGIT_BRANCH=${GIT_BRANCH}
- -DBUILD_FULLNAME=${BUILD_FULLNAME}
- -DGIT_EXECUTABLE=${GIT_EXECUTABLE}
- -P ${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake
- DEPENDS
- # Check that the scm_rev files haven't changed
- "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in"
- "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h"
- # technically we should regenerate if the git version changed, but its not worth the effort imo
- "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
- VERBATIM
-)
+include(GenerateSCMRev)
add_library(common STATIC
+ address_space.cpp
+ address_space.h
algorithm.h
alignment.h
+ announce_multiplayer_room.h
assert.cpp
assert.h
+ atomic_helpers.h
atomic_ops.h
detached_tasks.cpp
detached_tasks.h
@@ -58,11 +40,13 @@ add_library(common STATIC
div_ceil.h
dynamic_library.cpp
dynamic_library.h
+ elf.h
error.cpp
error.h
expected.h
fiber.cpp
fiber.h
+ fixed_point.h
fs/file.cpp
fs/file.h
fs/fs.cpp
@@ -99,6 +83,8 @@ add_library(common STATIC
microprofile.cpp
microprofile.h
microprofileui.h
+ multi_level_page_table.cpp
+ multi_level_page_table.h
nvidia_flags.cpp
nvidia_flags.h
page_table.cpp
@@ -108,14 +94,16 @@ add_library(common STATIC
parent_of_member.h
point.h
quaternion.h
+ reader_writer_queue.h
ring_buffer.h
- scm_rev.cpp
+ ${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp
scm_rev.h
scope_exit.h
settings.cpp
settings.h
settings_input.cpp
settings_input.h
+ socket_types.h
spin_lock.cpp
spin_lock.h
stream.cpp
@@ -157,6 +145,7 @@ if(ARCHITECTURE_x86_64)
x64/xbyak_abi.h
x64/xbyak_util.h
)
+ target_link_libraries(common PRIVATE xbyak)
endif()
if (MSVC)
@@ -180,9 +169,10 @@ endif()
create_target_directory_groups(common)
target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads)
-target_link_libraries(common PRIVATE lz4::lz4 xbyak)
-if (MSVC)
+target_link_libraries(common PRIVATE lz4::lz4)
+if (TARGET zstd::zstd)
target_link_libraries(common PRIVATE zstd::zstd)
else()
- target_link_libraries(common PRIVATE zstd)
+ target_link_libraries(common PRIVATE
+ $<IF:$<TARGET_EXISTS:zstd::libzstd_shared>,zstd::libzstd_shared,zstd::libzstd_static>)
endif()
diff --git a/src/common/address_space.cpp b/src/common/address_space.cpp
new file mode 100644
index 000000000..866e78dbe
--- /dev/null
+++ b/src/common/address_space.cpp
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/address_space.inc"
+
+namespace Common {
+
+template class Common::FlatAllocator<u32, 0, 32>;
+
+}
diff --git a/src/common/address_space.h b/src/common/address_space.h
new file mode 100644
index 000000000..9222b2fdc
--- /dev/null
+++ b/src/common/address_space.h
@@ -0,0 +1,150 @@
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <concepts>
+#include <functional>
+#include <mutex>
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Common {
+template <typename VaType, size_t AddressSpaceBits>
+concept AddressSpaceValid = std::is_unsigned_v<VaType> && sizeof(VaType) * 8 >= AddressSpaceBits;
+
+struct EmptyStruct {};
+
+/**
+ * @brief FlatAddressSpaceMap provides a generic VA->PA mapping implementation using a sorted vector
+ */
+template <typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa,
+ bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo = EmptyStruct>
+requires AddressSpaceValid<VaType, AddressSpaceBits>
+class FlatAddressSpaceMap {
+public:
+ /// The maximum VA that this AS can technically reach
+ static constexpr VaType VaMaximum{(1ULL << (AddressSpaceBits - 1)) +
+ ((1ULL << (AddressSpaceBits - 1)) - 1)};
+
+ explicit FlatAddressSpaceMap(VaType va_limit,
+ std::function<void(VaType, VaType)> unmap_callback = {});
+
+ FlatAddressSpaceMap() = default;
+
+ void Map(VaType virt, PaType phys, VaType size, ExtraBlockInfo extra_info = {}) {
+ std::scoped_lock lock(block_mutex);
+ MapLocked(virt, phys, size, extra_info);
+ }
+
+ void Unmap(VaType virt, VaType size) {
+ std::scoped_lock lock(block_mutex);
+ UnmapLocked(virt, size);
+ }
+
+ VaType GetVALimit() const {
+ return va_limit;
+ }
+
+protected:
+ /**
+ * @brief Represents a block of memory in the AS, the physical mapping is contiguous until
+ * another block with a different phys address is hit
+ */
+ struct Block {
+ /// VA of the block
+ VaType virt{UnmappedVa};
+ /// PA of the block, will increase 1-1 with VA until a new block is encountered
+ PaType phys{UnmappedPa};
+ [[no_unique_address]] ExtraBlockInfo extra_info;
+
+ Block() = default;
+
+ Block(VaType virt_, PaType phys_, ExtraBlockInfo extra_info_)
+ : virt(virt_), phys(phys_), extra_info(extra_info_) {}
+
+ bool Valid() const {
+ return virt != UnmappedVa;
+ }
+
+ bool Mapped() const {
+ return phys != UnmappedPa;
+ }
+
+ bool Unmapped() const {
+ return phys == UnmappedPa;
+ }
+
+ bool operator<(const VaType& p_virt) const {
+ return virt < p_virt;
+ }
+ };
+
+ /**
+ * @brief Maps a PA range into the given AS region
+ * @note block_mutex MUST be locked when calling this
+ */
+ void MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInfo extra_info);
+
+ /**
+ * @brief Unmaps the given range and merges it with other unmapped regions
+ * @note block_mutex MUST be locked when calling this
+ */
+ void UnmapLocked(VaType virt, VaType size);
+
+ std::mutex block_mutex;
+ std::vector<Block> blocks{Block{}};
+
+ /// a soft limit on the maximum VA of the AS
+ VaType va_limit{VaMaximum};
+
+private:
+ /// Callback called when the mappings in an region have changed
+ std::function<void(VaType, VaType)> unmap_callback{};
+};
+
+/**
+ * @brief FlatMemoryManager specialises FlatAddressSpaceMap to work as an allocator, with an
+ * initial, fast linear pass and a subsequent slower pass that iterates until it finds a free block
+ */
+template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits>
+requires AddressSpaceValid<VaType, AddressSpaceBits>
+class FlatAllocator
+ : public FlatAddressSpaceMap<VaType, UnmappedVa, bool, false, false, AddressSpaceBits> {
+private:
+ using Base = FlatAddressSpaceMap<VaType, UnmappedVa, bool, false, false, AddressSpaceBits>;
+
+public:
+ explicit FlatAllocator(VaType virt_start, VaType va_limit = Base::VaMaximum);
+
+ /**
+ * @brief Allocates a region in the AS of the given size and returns its address
+ */
+ VaType Allocate(VaType size);
+
+ /**
+ * @brief Marks the given region in the AS as allocated
+ */
+ void AllocateFixed(VaType virt, VaType size);
+
+ /**
+ * @brief Frees an AS region so it can be used again
+ */
+ void Free(VaType virt, VaType size);
+
+ VaType GetVAStart() const {
+ return virt_start;
+ }
+
+private:
+ /// The base VA of the allocator, no allocations will be below this
+ VaType virt_start;
+
+ /**
+ * The end address for the initial linear allocation pass
+ * Once this reaches the AS limit the slower allocation path will be used
+ */
+ VaType current_linear_alloc_end;
+};
+} // namespace Common
diff --git a/src/common/address_space.inc b/src/common/address_space.inc
new file mode 100644
index 000000000..2195dabd5
--- /dev/null
+++ b/src/common/address_space.inc
@@ -0,0 +1,366 @@
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/address_space.h"
+#include "common/assert.h"
+
+#define MAP_MEMBER(returnType) \
+ template <typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa, \
+ bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo> \
+ requires AddressSpaceValid<VaType, AddressSpaceBits> returnType FlatAddressSpaceMap< \
+ VaType, UnmappedVa, PaType, UnmappedPa, PaContigSplit, AddressSpaceBits, ExtraBlockInfo>
+#define MAP_MEMBER_CONST() \
+ template <typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa, \
+ bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo> \
+ requires AddressSpaceValid<VaType, AddressSpaceBits> FlatAddressSpaceMap< \
+ VaType, UnmappedVa, PaType, UnmappedPa, PaContigSplit, AddressSpaceBits, ExtraBlockInfo>
+
+#define MM_MEMBER(returnType) \
+ template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> \
+ requires AddressSpaceValid<VaType, AddressSpaceBits> returnType \
+ FlatMemoryManager<VaType, UnmappedVa, AddressSpaceBits>
+
+#define ALLOC_MEMBER(returnType) \
+ template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> \
+ requires AddressSpaceValid<VaType, AddressSpaceBits> returnType \
+ FlatAllocator<VaType, UnmappedVa, AddressSpaceBits>
+#define ALLOC_MEMBER_CONST() \
+ template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> \
+ requires AddressSpaceValid<VaType, AddressSpaceBits> \
+ FlatAllocator<VaType, UnmappedVa, AddressSpaceBits>
+
+namespace Common {
+MAP_MEMBER_CONST()::FlatAddressSpaceMap(VaType va_limit_,
+ std::function<void(VaType, VaType)> unmap_callback_)
+ : va_limit{va_limit_}, unmap_callback{std::move(unmap_callback_)} {
+ if (va_limit > VaMaximum) {
+ ASSERT_MSG(false, "Invalid VA limit!");
+ }
+}
+
+MAP_MEMBER(void)::MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInfo extra_info) {
+ VaType virt_end{virt + size};
+
+ if (virt_end > va_limit) {
+ ASSERT_MSG(false,
+ "Trying to map a block past the VA limit: virt_end: 0x{:X}, va_limit: 0x{:X}",
+ virt_end, va_limit);
+ }
+
+ auto block_end_successor{std::lower_bound(blocks.begin(), blocks.end(), virt_end)};
+ if (block_end_successor == blocks.begin()) {
+ ASSERT_MSG(false, "Trying to map a block before the VA start: virt_end: 0x{:X}", virt_end);
+ }
+
+ auto block_end_predecessor{std::prev(block_end_successor)};
+
+ if (block_end_successor != blocks.end()) {
+ // We have blocks in front of us, if one is directly in front then we don't have to add a
+ // tail
+ if (block_end_successor->virt != virt_end) {
+ PaType tailPhys{[&]() -> PaType {
+ if constexpr (!PaContigSplit) {
+ // Always propagate unmapped regions rather than calculating offset
+ return block_end_predecessor->phys;
+ } else {
+ if (block_end_predecessor->Unmapped()) {
+ // Always propagate unmapped regions rather than calculating offset
+ return block_end_predecessor->phys;
+ } else {
+ return block_end_predecessor->phys + virt_end - block_end_predecessor->virt;
+ }
+ }
+ }()};
+
+ if (block_end_predecessor->virt >= virt) {
+ // If this block's start would be overlapped by the map then reuse it as a tail
+ // block
+ block_end_predecessor->virt = virt_end;
+ block_end_predecessor->phys = tailPhys;
+ block_end_predecessor->extra_info = block_end_predecessor->extra_info;
+
+ // No longer predecessor anymore
+ block_end_successor = block_end_predecessor--;
+ } else {
+ // Else insert a new one and we're done
+ blocks.insert(block_end_successor,
+ {Block(virt, phys, extra_info),
+ Block(virt_end, tailPhys, block_end_predecessor->extra_info)});
+ if (unmap_callback) {
+ unmap_callback(virt, size);
+ }
+
+ return;
+ }
+ }
+ } else {
+ // block_end_predecessor will always be unmapped as blocks has to be terminated by an
+ // unmapped chunk
+ if (block_end_predecessor != blocks.begin() && block_end_predecessor->virt >= virt) {
+ // Move the unmapped block start backwards
+ block_end_predecessor->virt = virt_end;
+
+ // No longer predecessor anymore
+ block_end_successor = block_end_predecessor--;
+ } else {
+ // Else insert a new one and we're done
+ blocks.insert(block_end_successor,
+ {Block(virt, phys, extra_info), Block(virt_end, UnmappedPa, {})});
+ if (unmap_callback) {
+ unmap_callback(virt, size);
+ }
+
+ return;
+ }
+ }
+
+ auto block_start_successor{block_end_successor};
+
+ // Walk the block vector to find the start successor as this is more efficient than another
+ // binary search in most scenarios
+ while (std::prev(block_start_successor)->virt >= virt) {
+ block_start_successor--;
+ }
+
+ // Check that the start successor is either the end block or something in between
+ if (block_start_successor->virt > virt_end) {
+ ASSERT_MSG(false, "Unsorted block in AS map: virt: 0x{:X}", block_start_successor->virt);
+ } else if (block_start_successor->virt == virt_end) {
+ // We need to create a new block as there are none spare that we would overwrite
+ blocks.insert(block_start_successor, Block(virt, phys, extra_info));
+ } else {
+ // Erase overwritten blocks
+ if (auto eraseStart{std::next(block_start_successor)}; eraseStart != block_end_successor) {
+ blocks.erase(eraseStart, block_end_successor);
+ }
+
+ // Reuse a block that would otherwise be overwritten as a start block
+ block_start_successor->virt = virt;
+ block_start_successor->phys = phys;
+ block_start_successor->extra_info = extra_info;
+ }
+
+ if (unmap_callback) {
+ unmap_callback(virt, size);
+ }
+}
+
+MAP_MEMBER(void)::UnmapLocked(VaType virt, VaType size) {
+ VaType virt_end{virt + size};
+
+ if (virt_end > va_limit) {
+ ASSERT_MSG(false,
+ "Trying to map a block past the VA limit: virt_end: 0x{:X}, va_limit: 0x{:X}",
+ virt_end, va_limit);
+ }
+
+ auto block_end_successor{std::lower_bound(blocks.begin(), blocks.end(), virt_end)};
+ if (block_end_successor == blocks.begin()) {
+ ASSERT_MSG(false, "Trying to unmap a block before the VA start: virt_end: 0x{:X}",
+ virt_end);
+ }
+
+ auto block_end_predecessor{std::prev(block_end_successor)};
+
+ auto walk_back_to_predecessor{[&](auto iter) {
+ while (iter->virt >= virt) {
+ iter--;
+ }
+
+ return iter;
+ }};
+
+ auto erase_blocks_with_end_unmapped{[&](auto unmappedEnd) {
+ auto block_start_predecessor{walk_back_to_predecessor(unmappedEnd)};
+ auto block_start_successor{std::next(block_start_predecessor)};
+
+ auto eraseEnd{[&]() {
+ if (block_start_predecessor->Unmapped()) {
+ // If the start predecessor is unmapped then we can erase everything in our region
+ // and be done
+ return std::next(unmappedEnd);
+ } else {
+ // Else reuse the end predecessor as the start of our unmapped region then erase all
+ // up to it
+ unmappedEnd->virt = virt;
+ return unmappedEnd;
+ }
+ }()};
+
+ // We can't have two unmapped regions after each other
+ if (eraseEnd != blocks.end() &&
+ (eraseEnd == block_start_successor ||
+ (block_start_predecessor->Unmapped() && eraseEnd->Unmapped()))) {
+ ASSERT_MSG(false, "Multiple contiguous unmapped regions are unsupported!");
+ }
+
+ blocks.erase(block_start_successor, eraseEnd);
+ }};
+
+ // We can avoid any splitting logic if these are the case
+ if (block_end_predecessor->Unmapped()) {
+ if (block_end_predecessor->virt > virt) {
+ erase_blocks_with_end_unmapped(block_end_predecessor);
+ }
+
+ if (unmap_callback) {
+ unmap_callback(virt, size);
+ }
+
+ return; // The region is unmapped, bail out early
+ } else if (block_end_successor->virt == virt_end && block_end_successor->Unmapped()) {
+ erase_blocks_with_end_unmapped(block_end_successor);
+
+ if (unmap_callback) {
+ unmap_callback(virt, size);
+ }
+
+ return; // The region is unmapped here and doesn't need splitting, bail out early
+ } else if (block_end_successor == blocks.end()) {
+ // This should never happen as the end should always follow an unmapped block
+ ASSERT_MSG(false, "Unexpected Memory Manager state!");
+ } else if (block_end_successor->virt != virt_end) {
+ // If one block is directly in front then we don't have to add a tail
+
+ // The previous block is mapped so we will need to add a tail with an offset
+ PaType tailPhys{[&]() {
+ if constexpr (PaContigSplit) {
+ return block_end_predecessor->phys + virt_end - block_end_predecessor->virt;
+ } else {
+ return block_end_predecessor->phys;
+ }
+ }()};
+
+ if (block_end_predecessor->virt >= virt) {
+ // If this block's start would be overlapped by the unmap then reuse it as a tail block
+ block_end_predecessor->virt = virt_end;
+ block_end_predecessor->phys = tailPhys;
+
+ // No longer predecessor anymore
+ block_end_successor = block_end_predecessor--;
+ } else {
+ blocks.insert(block_end_successor,
+ {Block(virt, UnmappedPa, {}),
+ Block(virt_end, tailPhys, block_end_predecessor->extra_info)});
+ if (unmap_callback) {
+ unmap_callback(virt, size);
+ }
+
+ // The previous block is mapped and ends before
+ return;
+ }
+ }
+
+ // Walk the block vector to find the start predecessor as this is more efficient than another
+ // binary search in most scenarios
+ auto block_start_predecessor{walk_back_to_predecessor(block_end_successor)};
+ auto block_start_successor{std::next(block_start_predecessor)};
+
+ if (block_start_successor->virt > virt_end) {
+ ASSERT_MSG(false, "Unsorted block in AS map: virt: 0x{:X}", block_start_successor->virt);
+ } else if (block_start_successor->virt == virt_end) {
+ // There are no blocks between the start and the end that would let us skip inserting a new
+ // one for head
+
+ // The previous block is may be unmapped, if so we don't need to insert any unmaps after it
+ if (block_start_predecessor->Mapped()) {
+ blocks.insert(block_start_successor, Block(virt, UnmappedPa, {}));
+ }
+ } else if (block_start_predecessor->Unmapped()) {
+ // If the previous block is unmapped
+ blocks.erase(block_start_successor, block_end_predecessor);
+ } else {
+ // Erase overwritten blocks, skipping the first one as we have written the unmapped start
+ // block there
+ if (auto eraseStart{std::next(block_start_successor)}; eraseStart != block_end_successor) {
+ blocks.erase(eraseStart, block_end_successor);
+ }
+
+ // Add in the unmapped block header
+ block_start_successor->virt = virt;
+ block_start_successor->phys = UnmappedPa;
+ }
+
+ if (unmap_callback)
+ unmap_callback(virt, size);
+}
+
+ALLOC_MEMBER_CONST()::FlatAllocator(VaType virt_start_, VaType va_limit_)
+ : Base{va_limit_}, virt_start{virt_start_}, current_linear_alloc_end{virt_start_} {}
+
+ALLOC_MEMBER(VaType)::Allocate(VaType size) {
+ std::scoped_lock lock(this->block_mutex);
+
+ VaType alloc_start{UnmappedVa};
+ VaType alloc_end{current_linear_alloc_end + size};
+
+ // Avoid searching backwards in the address space if possible
+ if (alloc_end >= current_linear_alloc_end && alloc_end <= this->va_limit) {
+ auto alloc_end_successor{
+ std::lower_bound(this->blocks.begin(), this->blocks.end(), alloc_end)};
+ if (alloc_end_successor == this->blocks.begin()) {
+ ASSERT_MSG(false, "First block in AS map is invalid!");
+ }
+
+ auto alloc_end_predecessor{std::prev(alloc_end_successor)};
+ if (alloc_end_predecessor->virt <= current_linear_alloc_end) {
+ alloc_start = current_linear_alloc_end;
+ } else {
+ // Skip over fixed any mappings in front of us
+ while (alloc_end_successor != this->blocks.end()) {
+ if (alloc_end_successor->virt - alloc_end_predecessor->virt < size ||
+ alloc_end_predecessor->Mapped()) {
+ alloc_start = alloc_end_predecessor->virt;
+ break;
+ }
+
+ alloc_end_predecessor = alloc_end_successor++;
+
+ // Use the VA limit to calculate if we can fit in the final block since it has no
+ // successor
+ if (alloc_end_successor == this->blocks.end()) {
+ alloc_end = alloc_end_predecessor->virt + size;
+
+ if (alloc_end >= alloc_end_predecessor->virt && alloc_end <= this->va_limit) {
+ alloc_start = alloc_end_predecessor->virt;
+ }
+ }
+ }
+ }
+ }
+
+ if (alloc_start != UnmappedVa) {
+ current_linear_alloc_end = alloc_start + size;
+ } else { // If linear allocation overflows the AS then find a gap
+ if (this->blocks.size() <= 2) {
+ ASSERT_MSG(false, "Unexpected allocator state!");
+ }
+
+ auto search_predecessor{this->blocks.begin()};
+ auto search_successor{std::next(search_predecessor)};
+
+ while (search_successor != this->blocks.end() &&
+ (search_successor->virt - search_predecessor->virt < size ||
+ search_predecessor->Mapped())) {
+ search_predecessor = search_successor++;
+ }
+
+ if (search_successor != this->blocks.end()) {
+ alloc_start = search_predecessor->virt;
+ } else {
+ return {}; // AS is full
+ }
+ }
+
+ this->MapLocked(alloc_start, true, size, {});
+ return alloc_start;
+}
+
+ALLOC_MEMBER(void)::AllocateFixed(VaType virt, VaType size) {
+ this->Map(virt, true, size);
+}
+
+ALLOC_MEMBER(void)::Free(VaType virt, VaType size) {
+ this->Unmap(virt, size);
+}
+} // namespace Common
diff --git a/src/common/algorithm.h b/src/common/algorithm.h
index 4804a3421..c27c9241d 100644
--- a/src/common/algorithm.h
+++ b/src/common/algorithm.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -25,4 +24,12 @@ template <class ForwardIt, class T, class Compare = std::less<>>
return first != last && !comp(value, *first) ? first : last;
}
+template <typename T, typename Func, typename... Args>
+T FoldRight(T initial_value, Func&& func, Args&&... args) {
+ T value{initial_value};
+ const auto high_func = [&value, &func]<typename U>(U x) { value = func(value, x); };
+ (std::invoke(high_func, std::forward<Args>(args)), ...);
+ return value;
+}
+
} // namespace Common
diff --git a/src/common/alignment.h b/src/common/alignment.h
index 8570c7d3c..7e897334b 100644
--- a/src/common/alignment.h
+++ b/src/common/alignment.h
@@ -1,4 +1,5 @@
-// This file is under the public domain.
+// SPDX-FileCopyrightText: 2014 Jannik Vogel <email@jannikvogel.de>
+// SPDX-License-Identifier: CC0-1.0
#pragma once
diff --git a/src/common/announce_multiplayer_room.h b/src/common/announce_multiplayer_room.h
new file mode 100644
index 000000000..4a3100fa4
--- /dev/null
+++ b/src/common/announce_multiplayer_room.h
@@ -0,0 +1,140 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <functional>
+#include <string>
+#include <vector>
+#include "common/common_types.h"
+#include "common/socket_types.h"
+#include "web_service/web_result.h"
+
+namespace AnnounceMultiplayerRoom {
+
+struct GameInfo {
+ std::string name{""};
+ u64 id{0};
+ std::string version{""};
+};
+
+struct Member {
+ std::string username;
+ std::string nickname;
+ std::string display_name;
+ std::string avatar_url;
+ Network::IPv4Address fake_ip;
+ GameInfo game;
+};
+
+struct RoomInformation {
+ std::string name; ///< Name of the server
+ std::string description; ///< Server description
+ u32 member_slots; ///< Maximum number of members in this room
+ u16 port; ///< The port of this room
+ GameInfo preferred_game; ///< Game to advertise that you want to play
+ std::string host_username; ///< Forum username of the host
+ bool enable_yuzu_mods; ///< Allow yuzu Moderators to moderate on this room
+};
+
+struct Room {
+ RoomInformation information;
+
+ std::string id;
+ std::string verify_uid; ///< UID used for verification
+ std::string ip;
+ u32 net_version;
+ bool has_password;
+
+ std::vector<Member> members;
+};
+using RoomList = std::vector<Room>;
+
+/**
+ * A AnnounceMultiplayerRoom interface class. A backend to submit/get to/from a web service should
+ * implement this interface.
+ */
+class Backend {
+public:
+ virtual ~Backend() = default;
+
+ /**
+ * Sets the Information that gets used for the announce
+ * @param uid The Id of the room
+ * @param name The name of the room
+ * @param description The room description
+ * @param port The port of the room
+ * @param net_version The version of the libNetwork that gets used
+ * @param has_password True if the room is passowrd protected
+ * @param preferred_game The preferred game of the room
+ * @param preferred_game_id The title id of the preferred game
+ */
+ virtual void SetRoomInformation(const std::string& name, const std::string& description,
+ const u16 port, const u32 max_player, const u32 net_version,
+ const bool has_password, const GameInfo& preferred_game) = 0;
+ /**
+ * Adds a player information to the data that gets announced
+ * @param member The player to add
+ */
+ virtual void AddPlayer(const Member& member) = 0;
+
+ /**
+ * Updates the data in the announce service. Re-register the room when required.
+ * @result The result of the update attempt
+ */
+ virtual WebService::WebResult Update() = 0;
+
+ /**
+ * Registers the data in the announce service
+ * @result The result of the register attempt. When the result code is Success, A global Guid of
+ * the room which may be used for verification will be in the result's returned_data.
+ */
+ virtual WebService::WebResult Register() = 0;
+
+ /**
+ * Empties the stored players
+ */
+ virtual void ClearPlayers() = 0;
+
+ /**
+ * Get the room information from the announce service
+ * @result A list of all rooms the announce service has
+ */
+ virtual RoomList GetRoomList() = 0;
+
+ /**
+ * Sends a delete message to the announce service
+ */
+ virtual void Delete() = 0;
+};
+
+/**
+ * Empty implementation of AnnounceMultiplayerRoom interface that drops all data. Used when a
+ * functional backend implementation is not available.
+ */
+class NullBackend : public Backend {
+public:
+ ~NullBackend() = default;
+ void SetRoomInformation(const std::string& /*name*/, const std::string& /*description*/,
+ const u16 /*port*/, const u32 /*max_player*/, const u32 /*net_version*/,
+ const bool /*has_password*/,
+ const GameInfo& /*preferred_game*/) override {}
+ void AddPlayer(const Member& /*member*/) override {}
+ WebService::WebResult Update() override {
+ return WebService::WebResult{WebService::WebResult::Code::NoWebservice,
+ "WebService is missing", ""};
+ }
+ WebService::WebResult Register() override {
+ return WebService::WebResult{WebService::WebResult::Code::NoWebservice,
+ "WebService is missing", ""};
+ }
+ void ClearPlayers() override {}
+ RoomList GetRoomList() override {
+ return RoomList{};
+ }
+
+ void Delete() override {}
+};
+
+} // namespace AnnounceMultiplayerRoom
diff --git a/src/common/assert.cpp b/src/common/assert.cpp
index 72f1121aa..6026b7dc2 100644
--- a/src/common/assert.cpp
+++ b/src/common/assert.cpp
@@ -1,14 +1,18 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/settings.h"
-void assert_handle_failure() {
+void assert_fail_impl() {
if (Settings::values.use_debug_asserts) {
Crash();
}
}
+
+[[noreturn]] void unreachable_impl() {
+ Crash();
+ throw std::runtime_error("Unreachable code");
+}
diff --git a/src/common/assert.h b/src/common/assert.h
index 33060d865..8c927fcc0 100644
--- a/src/common/assert.h
+++ b/src/common/assert.h
@@ -1,6 +1,6 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -9,44 +9,43 @@
// Sometimes we want to try to continue even after hitting an assert.
// However touching this file yields a global recompilation as this header is included almost
// everywhere. So let's just move the handling of the failed assert to a single cpp file.
-void assert_handle_failure();
-// For asserts we'd like to keep all the junk executed when an assert happens away from the
-// important code in the function. One way of doing this is to put all the relevant code inside a
-// lambda and force the compiler to not inline it. Unfortunately, MSVC seems to have no syntax to
-// specify __declspec on lambda functions, so what we do instead is define a noinline wrapper
-// template that calls the lambda. This seems to generate an extra instruction at the call-site
-// compared to the ideal implementation (which wouldn't support ASSERT_MSG parameters), but is good
-// enough for our purposes.
-template <typename Fn>
-#if defined(_MSC_VER)
-[[msvc::noinline]]
-#elif defined(__GNUC__)
-[[gnu::cold, gnu::noinline]]
+void assert_fail_impl();
+[[noreturn]] void unreachable_impl();
+
+#ifdef _MSC_VER
+#define YUZU_NO_INLINE __declspec(noinline)
+#else
+#define YUZU_NO_INLINE __attribute__((noinline))
#endif
-static void
-assert_noinline_call(const Fn& fn) {
- fn();
- assert_handle_failure();
-}
#define ASSERT(_a_) \
- do \
- if (!(_a_)) { \
- assert_noinline_call([] { LOG_CRITICAL(Debug, "Assertion Failed!"); }); \
+ ([&]() YUZU_NO_INLINE { \
+ if (!(_a_)) [[unlikely]] { \
+ LOG_CRITICAL(Debug, "Assertion Failed!"); \
+ assert_fail_impl(); \
} \
- while (0)
+ }())
#define ASSERT_MSG(_a_, ...) \
- do \
- if (!(_a_)) { \
- assert_noinline_call([&] { LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); }); \
+ ([&]() YUZU_NO_INLINE { \
+ if (!(_a_)) [[unlikely]] { \
+ LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \
+ assert_fail_impl(); \
} \
- while (0)
+ }())
+
+#define UNREACHABLE() \
+ do { \
+ LOG_CRITICAL(Debug, "Unreachable code!"); \
+ unreachable_impl(); \
+ } while (0)
-#define UNREACHABLE() assert_noinline_call([] { LOG_CRITICAL(Debug, "Unreachable code!"); })
#define UNREACHABLE_MSG(...) \
- assert_noinline_call([&] { LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); })
+ do { \
+ LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); \
+ unreachable_impl(); \
+ } while (0)
#ifdef _DEBUG
#define DEBUG_ASSERT(_a_) ASSERT(_a_)
diff --git a/src/common/atomic_helpers.h b/src/common/atomic_helpers.h
new file mode 100644
index 000000000..bef5015c1
--- /dev/null
+++ b/src/common/atomic_helpers.h
@@ -0,0 +1,775 @@
+// SPDX-FileCopyrightText: 2013-2016 Cameron Desrochers
+// SPDX-FileCopyrightText: 2015 Jeff Preshing
+// SPDX-License-Identifier: BSD-2-Clause AND Zlib
+
+// Distributed under the simplified BSD license (see the license file that
+// should have come with this header).
+// Uses Jeff Preshing's semaphore implementation (under the terms of its
+// separate zlib license, embedded below).
+
+#pragma once
+
+// Provides portable (VC++2010+, Intel ICC 13, GCC 4.7+, and anything C++11 compliant)
+// implementation of low-level memory barriers, plus a few semi-portable utility macros (for
+// inlining and alignment). Also has a basic atomic type (limited to hardware-supported atomics with
+// no memory ordering guarantees). Uses the AE_* prefix for macros (historical reasons), and the
+// "moodycamel" namespace for symbols.
+
+#include <cassert>
+#include <cerrno>
+#include <cstdint>
+#include <ctime>
+#include <type_traits>
+
+// Platform detection
+#if defined(__INTEL_COMPILER)
+#define AE_ICC
+#elif defined(_MSC_VER)
+#define AE_VCPP
+#elif defined(__GNUC__)
+#define AE_GCC
+#endif
+
+#if defined(_M_IA64) || defined(__ia64__)
+#define AE_ARCH_IA64
+#elif defined(_WIN64) || defined(__amd64__) || defined(_M_X64) || defined(__x86_64__)
+#define AE_ARCH_X64
+#elif defined(_M_IX86) || defined(__i386__)
+#define AE_ARCH_X86
+#elif defined(_M_PPC) || defined(__powerpc__)
+#define AE_ARCH_PPC
+#else
+#define AE_ARCH_UNKNOWN
+#endif
+
+// AE_UNUSED
+#define AE_UNUSED(x) ((void)x)
+
+// AE_NO_TSAN/AE_TSAN_ANNOTATE_*
+#if defined(__has_feature)
+#if __has_feature(thread_sanitizer)
+#if __cplusplus >= 201703L // inline variables require C++17
+namespace Common {
+inline int ae_tsan_global;
+}
+#define AE_TSAN_ANNOTATE_RELEASE() \
+ AnnotateHappensBefore(__FILE__, __LINE__, (void*)(&::moodycamel::ae_tsan_global))
+#define AE_TSAN_ANNOTATE_ACQUIRE() \
+ AnnotateHappensAfter(__FILE__, __LINE__, (void*)(&::moodycamel::ae_tsan_global))
+extern "C" void AnnotateHappensBefore(const char*, int, void*);
+extern "C" void AnnotateHappensAfter(const char*, int, void*);
+#else // when we can't work with tsan, attempt to disable its warnings
+#define AE_NO_TSAN __attribute__((no_sanitize("thread")))
+#endif
+#endif
+#endif
+#ifndef AE_NO_TSAN
+#define AE_NO_TSAN
+#endif
+#ifndef AE_TSAN_ANNOTATE_RELEASE
+#define AE_TSAN_ANNOTATE_RELEASE()
+#define AE_TSAN_ANNOTATE_ACQUIRE()
+#endif
+
+// AE_FORCEINLINE
+#if defined(AE_VCPP) || defined(AE_ICC)
+#define AE_FORCEINLINE __forceinline
+#elif defined(AE_GCC)
+//#define AE_FORCEINLINE __attribute__((always_inline))
+#define AE_FORCEINLINE inline
+#else
+#define AE_FORCEINLINE inline
+#endif
+
+// AE_ALIGN
+#if defined(AE_VCPP) || defined(AE_ICC)
+#define AE_ALIGN(x) __declspec(align(x))
+#elif defined(AE_GCC)
+#define AE_ALIGN(x) __attribute__((aligned(x)))
+#else
+// Assume GCC compliant syntax...
+#define AE_ALIGN(x) __attribute__((aligned(x)))
+#endif
+
+// Portable atomic fences implemented below:
+
+namespace Common {
+
+enum memory_order {
+ memory_order_relaxed,
+ memory_order_acquire,
+ memory_order_release,
+ memory_order_acq_rel,
+ memory_order_seq_cst,
+
+ // memory_order_sync: Forces a full sync:
+ // #LoadLoad, #LoadStore, #StoreStore, and most significantly, #StoreLoad
+ memory_order_sync = memory_order_seq_cst
+};
+
+} // namespace Common
+
+#if (defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))) || \
+ (defined(AE_ICC) && __INTEL_COMPILER < 1600)
+// VS2010 and ICC13 don't support std::atomic_*_fence, implement our own fences
+
+#include <intrin.h>
+
+#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)
+#define AeFullSync _mm_mfence
+#define AeLiteSync _mm_mfence
+#elif defined(AE_ARCH_IA64)
+#define AeFullSync __mf
+#define AeLiteSync __mf
+#elif defined(AE_ARCH_PPC)
+#include <ppcintrinsics.h>
+#define AeFullSync __sync
+#define AeLiteSync __lwsync
+#endif
+
+#ifdef AE_VCPP
+#pragma warning(push)
+#pragma warning(disable : 4365) // Disable erroneous 'conversion from long to unsigned int,
+ // signed/unsigned mismatch' error when using `assert`
+#ifdef __cplusplus_cli
+#pragma managed(push, off)
+#endif
+#endif
+
+namespace Common {
+
+AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN {
+ switch (order) {
+ case memory_order_relaxed:
+ break;
+ case memory_order_acquire:
+ _ReadBarrier();
+ break;
+ case memory_order_release:
+ _WriteBarrier();
+ break;
+ case memory_order_acq_rel:
+ _ReadWriteBarrier();
+ break;
+ case memory_order_seq_cst:
+ _ReadWriteBarrier();
+ break;
+ default:
+ assert(false);
+ }
+}
+
+// x86/x64 have a strong memory model -- all loads and stores have
+// acquire and release semantics automatically (so only need compiler
+// barriers for those).
+#if defined(AE_ARCH_X86) || defined(AE_ARCH_X64)
+AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN {
+ switch (order) {
+ case memory_order_relaxed:
+ break;
+ case memory_order_acquire:
+ _ReadBarrier();
+ break;
+ case memory_order_release:
+ _WriteBarrier();
+ break;
+ case memory_order_acq_rel:
+ _ReadWriteBarrier();
+ break;
+ case memory_order_seq_cst:
+ _ReadWriteBarrier();
+ AeFullSync();
+ _ReadWriteBarrier();
+ break;
+ default:
+ assert(false);
+ }
+}
+#else
+AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN {
+ // Non-specialized arch, use heavier memory barriers everywhere just in case :-(
+ switch (order) {
+ case memory_order_relaxed:
+ break;
+ case memory_order_acquire:
+ _ReadBarrier();
+ AeLiteSync();
+ _ReadBarrier();
+ break;
+ case memory_order_release:
+ _WriteBarrier();
+ AeLiteSync();
+ _WriteBarrier();
+ break;
+ case memory_order_acq_rel:
+ _ReadWriteBarrier();
+ AeLiteSync();
+ _ReadWriteBarrier();
+ break;
+ case memory_order_seq_cst:
+ _ReadWriteBarrier();
+ AeFullSync();
+ _ReadWriteBarrier();
+ break;
+ default:
+ assert(false);
+ }
+}
+#endif
+} // namespace Common
+#else
+// Use standard library of atomics
+#include <atomic>
+
+namespace Common {
+
+AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN {
+ switch (order) {
+ case memory_order_relaxed:
+ break;
+ case memory_order_acquire:
+ std::atomic_signal_fence(std::memory_order_acquire);
+ break;
+ case memory_order_release:
+ std::atomic_signal_fence(std::memory_order_release);
+ break;
+ case memory_order_acq_rel:
+ std::atomic_signal_fence(std::memory_order_acq_rel);
+ break;
+ case memory_order_seq_cst:
+ std::atomic_signal_fence(std::memory_order_seq_cst);
+ break;
+ default:
+ assert(false);
+ }
+}
+
+AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN {
+ switch (order) {
+ case memory_order_relaxed:
+ break;
+ case memory_order_acquire:
+ AE_TSAN_ANNOTATE_ACQUIRE();
+ std::atomic_thread_fence(std::memory_order_acquire);
+ break;
+ case memory_order_release:
+ AE_TSAN_ANNOTATE_RELEASE();
+ std::atomic_thread_fence(std::memory_order_release);
+ break;
+ case memory_order_acq_rel:
+ AE_TSAN_ANNOTATE_ACQUIRE();
+ AE_TSAN_ANNOTATE_RELEASE();
+ std::atomic_thread_fence(std::memory_order_acq_rel);
+ break;
+ case memory_order_seq_cst:
+ AE_TSAN_ANNOTATE_ACQUIRE();
+ AE_TSAN_ANNOTATE_RELEASE();
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ break;
+ default:
+ assert(false);
+ }
+}
+
+} // namespace Common
+
+#endif
+
+#if !defined(AE_VCPP) || (_MSC_VER >= 1700 && !defined(__cplusplus_cli))
+#define AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
+#endif
+
+#ifdef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
+#include <atomic>
+#endif
+#include <utility>
+
+// WARNING: *NOT* A REPLACEMENT FOR std::atomic. READ CAREFULLY:
+// Provides basic support for atomic variables -- no memory ordering guarantees are provided.
+// The guarantee of atomicity is only made for types that already have atomic load and store
+// guarantees at the hardware level -- on most platforms this generally means aligned pointers and
+// integers (only).
+namespace Common {
+template <typename T>
+class weak_atomic {
+public:
+ AE_NO_TSAN weak_atomic() : value() {}
+#ifdef AE_VCPP
+#pragma warning(push)
+#pragma warning(disable : 4100) // Get rid of (erroneous) 'unreferenced formal parameter' warning
+#endif
+ template <typename U>
+ AE_NO_TSAN weak_atomic(U&& x) : value(std::forward<U>(x)) {}
+#ifdef __cplusplus_cli
+ // Work around bug with universal reference/nullptr combination that only appears when /clr is
+ // on
+ AE_NO_TSAN weak_atomic(nullptr_t) : value(nullptr) {}
+#endif
+ AE_NO_TSAN weak_atomic(weak_atomic const& other) : value(other.load()) {}
+ AE_NO_TSAN weak_atomic(weak_atomic&& other) : value(std::move(other.load())) {}
+#ifdef AE_VCPP
+#pragma warning(pop)
+#endif
+
+ AE_FORCEINLINE operator T() const AE_NO_TSAN {
+ return load();
+ }
+
+#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
+ template <typename U>
+ AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN {
+ value = std::forward<U>(x);
+ return *this;
+ }
+ AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN {
+ value = other.value;
+ return *this;
+ }
+
+ AE_FORCEINLINE T load() const AE_NO_TSAN {
+ return value;
+ }
+
+ AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN {
+#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)
+ if (sizeof(T) == 4)
+ return _InterlockedExchangeAdd((long volatile*)&value, (long)increment);
+#if defined(_M_AMD64)
+ else if (sizeof(T) == 8)
+ return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment);
+#endif
+#else
+#error Unsupported platform
+#endif
+ assert(false && "T must be either a 32 or 64 bit type");
+ return value;
+ }
+
+ AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN {
+#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)
+ if (sizeof(T) == 4)
+ return _InterlockedExchangeAdd((long volatile*)&value, (long)increment);
+#if defined(_M_AMD64)
+ else if (sizeof(T) == 8)
+ return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment);
+#endif
+#else
+#error Unsupported platform
+#endif
+ assert(false && "T must be either a 32 or 64 bit type");
+ return value;
+ }
+#else
+ template <typename U>
+ AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN {
+ value.store(std::forward<U>(x), std::memory_order_relaxed);
+ return *this;
+ }
+
+ AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN {
+ value.store(other.value.load(std::memory_order_relaxed), std::memory_order_relaxed);
+ return *this;
+ }
+
+ AE_FORCEINLINE T load() const AE_NO_TSAN {
+ return value.load(std::memory_order_relaxed);
+ }
+
+ AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN {
+ return value.fetch_add(increment, std::memory_order_acquire);
+ }
+
+ AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN {
+ return value.fetch_add(increment, std::memory_order_release);
+ }
+#endif
+
+private:
+#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
+ // No std::atomic support, but still need to circumvent compiler optimizations.
+ // `volatile` will make memory access slow, but is guaranteed to be reliable.
+ volatile T value;
+#else
+ std::atomic<T> value;
+#endif
+};
+
+} // namespace Common
+
+// Portable single-producer, single-consumer semaphore below:
+
+#if defined(_WIN32)
+// Avoid including windows.h in a header; we only need a handful of
+// items, so we'll redeclare them here (this is relatively safe since
+// the API generally has to remain stable between Windows versions).
+// I know this is an ugly hack but it still beats polluting the global
+// namespace with thousands of generic names or adding a .cpp for nothing.
+extern "C" {
+struct _SECURITY_ATTRIBUTES;
+__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes,
+ long lInitialCount, long lMaximumCount,
+ const wchar_t* lpName);
+__declspec(dllimport) int __stdcall CloseHandle(void* hObject);
+__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle,
+ unsigned long dwMilliseconds);
+__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount,
+ long* lpPreviousCount);
+}
+#elif defined(__MACH__)
+#include <mach/mach.h>
+#elif defined(__unix__)
+#include <semaphore.h>
+#elif defined(FREERTOS)
+#include <FreeRTOS.h>
+#include <semphr.h>
+#include <task.h>
+#endif
+
+namespace Common {
+// Code in the spsc_sema namespace below is an adaptation of Jeff Preshing's
+// portable + lightweight semaphore implementations, originally from
+// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h
+// LICENSE:
+// Copyright (c) 2015 Jeff Preshing
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgement in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+namespace spsc_sema {
+#if defined(_WIN32)
+class Semaphore {
+private:
+ void* m_hSema;
+
+ Semaphore(const Semaphore& other);
+ Semaphore& operator=(const Semaphore& other);
+
+public:
+ AE_NO_TSAN Semaphore(int initialCount = 0) : m_hSema() {
+ assert(initialCount >= 0);
+ const long maxLong = 0x7fffffff;
+ m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr);
+ assert(m_hSema);
+ }
+
+ AE_NO_TSAN ~Semaphore() {
+ CloseHandle(m_hSema);
+ }
+
+ bool wait() AE_NO_TSAN {
+ const unsigned long infinite = 0xffffffff;
+ return WaitForSingleObject(m_hSema, infinite) == 0;
+ }
+
+ bool try_wait() AE_NO_TSAN {
+ return WaitForSingleObject(m_hSema, 0) == 0;
+ }
+
+ bool timed_wait(std::uint64_t usecs) AE_NO_TSAN {
+ return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0;
+ }
+
+ void signal(int count = 1) AE_NO_TSAN {
+ while (!ReleaseSemaphore(m_hSema, count, nullptr))
+ ;
+ }
+};
+#elif defined(__MACH__)
+//---------------------------------------------------------
+// Semaphore (Apple iOS and OSX)
+// Can't use POSIX semaphores due to
+// http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html
+//---------------------------------------------------------
+class Semaphore {
+private:
+ semaphore_t m_sema;
+
+ Semaphore(const Semaphore& other);
+ Semaphore& operator=(const Semaphore& other);
+
+public:
+ AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() {
+ assert(initialCount >= 0);
+ kern_return_t rc =
+ semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount);
+ assert(rc == KERN_SUCCESS);
+ AE_UNUSED(rc);
+ }
+
+ AE_NO_TSAN ~Semaphore() {
+ semaphore_destroy(mach_task_self(), m_sema);
+ }
+
+ bool wait() AE_NO_TSAN {
+ return semaphore_wait(m_sema) == KERN_SUCCESS;
+ }
+
+ bool try_wait() AE_NO_TSAN {
+ return timed_wait(0);
+ }
+
+ bool timed_wait(std::uint64_t timeout_usecs) AE_NO_TSAN {
+ mach_timespec_t ts;
+ ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000);
+ ts.tv_nsec = static_cast<int>((timeout_usecs % 1000000) * 1000);
+
+ // added in OSX 10.10:
+ // https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html
+ kern_return_t rc = semaphore_timedwait(m_sema, ts);
+ return rc == KERN_SUCCESS;
+ }
+
+ void signal() AE_NO_TSAN {
+ while (semaphore_signal(m_sema) != KERN_SUCCESS)
+ ;
+ }
+
+ void signal(int count) AE_NO_TSAN {
+ while (count-- > 0) {
+ while (semaphore_signal(m_sema) != KERN_SUCCESS)
+ ;
+ }
+ }
+};
+#elif defined(__unix__)
+//---------------------------------------------------------
+// Semaphore (POSIX, Linux)
+//---------------------------------------------------------
+class Semaphore {
+private:
+ sem_t m_sema;
+
+ Semaphore(const Semaphore& other);
+ Semaphore& operator=(const Semaphore& other);
+
+public:
+ AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() {
+ assert(initialCount >= 0);
+ int rc = sem_init(&m_sema, 0, static_cast<unsigned int>(initialCount));
+ assert(rc == 0);
+ AE_UNUSED(rc);
+ }
+
+ AE_NO_TSAN ~Semaphore() {
+ sem_destroy(&m_sema);
+ }
+
+ bool wait() AE_NO_TSAN {
+ // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error
+ int rc;
+ do {
+ rc = sem_wait(&m_sema);
+ } while (rc == -1 && errno == EINTR);
+ return rc == 0;
+ }
+
+ bool try_wait() AE_NO_TSAN {
+ int rc;
+ do {
+ rc = sem_trywait(&m_sema);
+ } while (rc == -1 && errno == EINTR);
+ return rc == 0;
+ }
+
+ bool timed_wait(std::uint64_t usecs) AE_NO_TSAN {
+ struct timespec ts;
+ const int usecs_in_1_sec = 1000000;
+ const int nsecs_in_1_sec = 1000000000;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_sec += static_cast<time_t>(usecs / usecs_in_1_sec);
+ ts.tv_nsec += static_cast<long>(usecs % usecs_in_1_sec) * 1000;
+ // sem_timedwait bombs if you have more than 1e9 in tv_nsec
+ // so we have to clean things up before passing it in
+ if (ts.tv_nsec >= nsecs_in_1_sec) {
+ ts.tv_nsec -= nsecs_in_1_sec;
+ ++ts.tv_sec;
+ }
+
+ int rc;
+ do {
+ rc = sem_timedwait(&m_sema, &ts);
+ } while (rc == -1 && errno == EINTR);
+ return rc == 0;
+ }
+
+ void signal() AE_NO_TSAN {
+ while (sem_post(&m_sema) == -1)
+ ;
+ }
+
+ void signal(int count) AE_NO_TSAN {
+ while (count-- > 0) {
+ while (sem_post(&m_sema) == -1)
+ ;
+ }
+ }
+};
+#elif defined(FREERTOS)
+//---------------------------------------------------------
+// Semaphore (FreeRTOS)
+//---------------------------------------------------------
+class Semaphore {
+private:
+ SemaphoreHandle_t m_sema;
+
+ Semaphore(const Semaphore& other);
+ Semaphore& operator=(const Semaphore& other);
+
+public:
+ AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() {
+ assert(initialCount >= 0);
+ m_sema = xSemaphoreCreateCounting(static_cast<UBaseType_t>(~0ull),
+ static_cast<UBaseType_t>(initialCount));
+ assert(m_sema);
+ }
+
+ AE_NO_TSAN ~Semaphore() {
+ vSemaphoreDelete(m_sema);
+ }
+
+ bool wait() AE_NO_TSAN {
+ return xSemaphoreTake(m_sema, portMAX_DELAY) == pdTRUE;
+ }
+
+ bool try_wait() AE_NO_TSAN {
+ // Note: In an ISR context, if this causes a task to unblock,
+ // the caller won't know about it
+ if (xPortIsInsideInterrupt())
+ return xSemaphoreTakeFromISR(m_sema, NULL) == pdTRUE;
+ return xSemaphoreTake(m_sema, 0) == pdTRUE;
+ }
+
+ bool timed_wait(std::uint64_t usecs) AE_NO_TSAN {
+ std::uint64_t msecs = usecs / 1000;
+ TickType_t ticks = static_cast<TickType_t>(msecs / portTICK_PERIOD_MS);
+ if (ticks == 0)
+ return try_wait();
+ return xSemaphoreTake(m_sema, ticks) == pdTRUE;
+ }
+
+ void signal() AE_NO_TSAN {
+ // Note: In an ISR context, if this causes a task to unblock,
+ // the caller won't know about it
+ BaseType_t rc;
+ if (xPortIsInsideInterrupt())
+ rc = xSemaphoreGiveFromISR(m_sema, NULL);
+ else
+ rc = xSemaphoreGive(m_sema);
+ assert(rc == pdTRUE);
+ AE_UNUSED(rc);
+ }
+
+ void signal(int count) AE_NO_TSAN {
+ while (count-- > 0)
+ signal();
+ }
+};
+#else
+#error Unsupported platform! (No semaphore wrapper available)
+#endif
+
+//---------------------------------------------------------
+// LightweightSemaphore
+//---------------------------------------------------------
+class LightweightSemaphore {
+public:
+ typedef std::make_signed<std::size_t>::type ssize_t;
+
+private:
+ weak_atomic<ssize_t> m_count;
+ Semaphore m_sema;
+
+ bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) AE_NO_TSAN {
+ ssize_t oldCount;
+ // Is there a better way to set the initial spin count?
+ // If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC,
+ // as threads start hitting the kernel semaphore.
+ int spin = 1024;
+ while (--spin >= 0) {
+ if (m_count.load() > 0) {
+ m_count.fetch_add_acquire(-1);
+ return true;
+ }
+ compiler_fence(memory_order_acquire); // Prevent the compiler from collapsing the loop.
+ }
+ oldCount = m_count.fetch_add_acquire(-1);
+ if (oldCount > 0)
+ return true;
+ if (timeout_usecs < 0) {
+ if (m_sema.wait())
+ return true;
+ }
+ if (timeout_usecs > 0 && m_sema.timed_wait(static_cast<uint64_t>(timeout_usecs)))
+ return true;
+ // At this point, we've timed out waiting for the semaphore, but the
+ // count is still decremented indicating we may still be waiting on
+ // it. So we have to re-adjust the count, but only if the semaphore
+ // wasn't signaled enough times for us too since then. If it was, we
+ // need to release the semaphore too.
+ while (true) {
+ oldCount = m_count.fetch_add_release(1);
+ if (oldCount < 0)
+ return false; // successfully restored things to the way they were
+ // Oh, the producer thread just signaled the semaphore after all. Try again:
+ oldCount = m_count.fetch_add_acquire(-1);
+ if (oldCount > 0 && m_sema.try_wait())
+ return true;
+ }
+ }
+
+public:
+ AE_NO_TSAN LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount), m_sema() {
+ assert(initialCount >= 0);
+ }
+
+ bool tryWait() AE_NO_TSAN {
+ if (m_count.load() > 0) {
+ m_count.fetch_add_acquire(-1);
+ return true;
+ }
+ return false;
+ }
+
+ bool wait() AE_NO_TSAN {
+ return tryWait() || waitWithPartialSpinning();
+ }
+
+ bool wait(std::int64_t timeout_usecs) AE_NO_TSAN {
+ return tryWait() || waitWithPartialSpinning(timeout_usecs);
+ }
+
+ void signal(ssize_t count = 1) AE_NO_TSAN {
+ assert(count >= 0);
+ ssize_t oldCount = m_count.fetch_add_release(count);
+ assert(oldCount >= -1);
+ if (oldCount < 0) {
+ m_sema.signal(1);
+ }
+ }
+
+ std::size_t availableApprox() const AE_NO_TSAN {
+ ssize_t count = m_count.load();
+ return count > 0 ? static_cast<std::size_t>(count) : 0;
+ }
+};
+} // namespace spsc_sema
+} // namespace Common
+
+#if defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))
+#pragma warning(pop)
+#ifdef __cplusplus_cli
+#pragma managed(pop)
+#endif
+#endif
diff --git a/src/common/atomic_ops.h b/src/common/atomic_ops.h
index 2b1f515e8..c18bb33c4 100644
--- a/src/common/atomic_ops.h
+++ b/src/common/atomic_ops.h
@@ -1,16 +1,14 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <cstring>
-#include <memory>
-
#include "common/common_types.h"
#if _MSC_VER
#include <intrin.h>
+#else
+#include <cstring>
#endif
namespace Common {
@@ -47,6 +45,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) {
@@ -73,6 +115,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/bit_cast.h b/src/common/bit_cast.h
index a32a063d1..535148b4d 100644
--- a/src/common/bit_cast.h
+++ b/src/common/bit_cast.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/bit_field.h b/src/common/bit_field.h
index 0f0661172..7e1df62b1 100644
--- a/src/common/bit_field.h
+++ b/src/common/bit_field.h
@@ -1,39 +1,12 @@
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-// Copyright 2014 Tony Wasserka
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above copyright
-// notice, this list of conditions and the following disclaimer in the
-// documentation and/or other materials provided with the distribution.
-// * Neither the name of the owner nor the names of its contributors may
-// be used to endorse or promote products derived from this software
-// without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// SPDX-FileCopyrightText: 2014 Tony Wasserka
+// SPDX-FileCopyrightText: 2014 Dolphin Emulator Project
+// SPDX-License-Identifier: BSD-3-Clause AND GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <limits>
#include <type_traits>
-#include "common/common_funcs.h"
#include "common/swap.h"
/*
@@ -173,7 +146,16 @@ public:
}
constexpr void Assign(const T& value) {
+#ifdef _MSC_VER
storage = static_cast<StorageType>((storage & ~mask) | FormatValue(value));
+#else
+ // Explicitly reload with memcpy to avoid compiler aliasing quirks
+ // regarding optimization: GCC/Clang clobber chained stores to
+ // different bitfields in the same struct with the last value.
+ StorageTypeWithEndian storage_;
+ std::memcpy(&storage_, &storage, sizeof(storage_));
+ storage = static_cast<StorageType>((storage_ & ~mask) | FormatValue(value));
+#endif
}
[[nodiscard]] constexpr T Value() const {
diff --git a/src/common/bit_set.h b/src/common/bit_set.h
index 9235ad412..74754504b 100644
--- a/src/common/bit_set.h
+++ b/src/common/bit_set.h
@@ -1,18 +1,5 @@
-/*
- * Copyright (c) 2018-2020 Atmosphère-NX
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms and conditions of the GNU General Public License,
- * version 2, as published by the Free Software Foundation.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/bit_util.h b/src/common/bit_util.h
index f50d3308a..e4e6287f3 100644
--- a/src/common/bit_util.h
+++ b/src/common/bit_util.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -57,4 +56,11 @@ requires std::is_integral_v<T>
return static_cast<T>(1ULL << ((8U * sizeof(T)) - std::countl_zero(value - 1U)));
}
+template <size_t bit_index, typename T>
+requires std::is_integral_v<T>
+[[nodiscard]] constexpr bool Bit(const T value) {
+ static_assert(bit_index < BitSize<T>(), "bit_index must be smaller than size of T");
+ return ((value >> bit_index) & T(1)) == T(1);
+}
+
} // namespace Common
diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h
new file mode 100644
index 000000000..7e465549b
--- /dev/null
+++ b/src/common/bounded_threadsafe_queue.h
@@ -0,0 +1,167 @@
+// SPDX-FileCopyrightText: Copyright (c) 2020 Erik Rigtorp <erik@rigtorp.se>
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <atomic>
+#include <bit>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <new>
+#include <stop_token>
+#include <type_traits>
+#include <utility>
+
+namespace Common {
+
+#if defined(__cpp_lib_hardware_interference_size)
+constexpr size_t hardware_interference_size = std::hardware_destructive_interference_size;
+#else
+constexpr size_t hardware_interference_size = 64;
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4324)
+#endif
+
+template <typename T, size_t capacity = 0x400>
+class MPSCQueue {
+public:
+ explicit MPSCQueue() : allocator{std::allocator<Slot<T>>()} {
+ // Allocate one extra slot to prevent false sharing on the last slot
+ slots = allocator.allocate(capacity + 1);
+ // Allocators are not required to honor alignment for over-aligned types
+ // (see http://eel.is/c++draft/allocator.requirements#10) so we verify
+ // alignment here
+ if (reinterpret_cast<uintptr_t>(slots) % alignof(Slot<T>) != 0) {
+ allocator.deallocate(slots, capacity + 1);
+ throw std::bad_alloc();
+ }
+ for (size_t i = 0; i < capacity; ++i) {
+ std::construct_at(&slots[i]);
+ }
+ static_assert(std::has_single_bit(capacity), "capacity must be an integer power of 2");
+ static_assert(alignof(Slot<T>) == hardware_interference_size,
+ "Slot must be aligned to cache line boundary to prevent false sharing");
+ static_assert(sizeof(Slot<T>) % hardware_interference_size == 0,
+ "Slot size must be a multiple of cache line size to prevent "
+ "false sharing between adjacent slots");
+ static_assert(sizeof(MPSCQueue) % hardware_interference_size == 0,
+ "Queue size must be a multiple of cache line size to "
+ "prevent false sharing between adjacent queues");
+ }
+
+ ~MPSCQueue() noexcept {
+ for (size_t i = 0; i < capacity; ++i) {
+ std::destroy_at(&slots[i]);
+ }
+ allocator.deallocate(slots, capacity + 1);
+ }
+
+ // The queue must be both non-copyable and non-movable
+ MPSCQueue(const MPSCQueue&) = delete;
+ MPSCQueue& operator=(const MPSCQueue&) = delete;
+
+ MPSCQueue(MPSCQueue&&) = delete;
+ MPSCQueue& operator=(MPSCQueue&&) = delete;
+
+ void Push(const T& v) noexcept {
+ static_assert(std::is_nothrow_copy_constructible_v<T>,
+ "T must be nothrow copy constructible");
+ emplace(v);
+ }
+
+ template <typename P, typename = std::enable_if_t<std::is_nothrow_constructible_v<T, P&&>>>
+ void Push(P&& v) noexcept {
+ emplace(std::forward<P>(v));
+ }
+
+ void Pop(T& v, std::stop_token stop) noexcept {
+ auto const tail = tail_.fetch_add(1);
+ auto& slot = slots[idx(tail)];
+ if (!slot.turn.test()) {
+ std::unique_lock lock{cv_mutex};
+ cv.wait(lock, stop, [&slot] { return slot.turn.test(); });
+ }
+ v = slot.move();
+ slot.destroy();
+ slot.turn.clear();
+ slot.turn.notify_one();
+ }
+
+private:
+ template <typename U = T>
+ struct Slot {
+ ~Slot() noexcept {
+ if (turn.test()) {
+ destroy();
+ }
+ }
+
+ template <typename... Args>
+ void construct(Args&&... args) noexcept {
+ static_assert(std::is_nothrow_constructible_v<U, Args&&...>,
+ "T must be nothrow constructible with Args&&...");
+ std::construct_at(reinterpret_cast<U*>(&storage), std::forward<Args>(args)...);
+ }
+
+ void destroy() noexcept {
+ static_assert(std::is_nothrow_destructible_v<U>, "T must be nothrow destructible");
+ std::destroy_at(reinterpret_cast<U*>(&storage));
+ }
+
+ U&& move() noexcept {
+ return reinterpret_cast<U&&>(storage);
+ }
+
+ // Align to avoid false sharing between adjacent slots
+ alignas(hardware_interference_size) std::atomic_flag turn{};
+ struct aligned_store {
+ struct type {
+ alignas(U) unsigned char data[sizeof(U)];
+ };
+ };
+ typename aligned_store::type storage;
+ };
+
+ template <typename... Args>
+ void emplace(Args&&... args) noexcept {
+ static_assert(std::is_nothrow_constructible_v<T, Args&&...>,
+ "T must be nothrow constructible with Args&&...");
+ auto const head = head_.fetch_add(1);
+ auto& slot = slots[idx(head)];
+ slot.turn.wait(true);
+ slot.construct(std::forward<Args>(args)...);
+ slot.turn.test_and_set();
+ cv.notify_one();
+ }
+
+ constexpr size_t idx(size_t i) const noexcept {
+ return i & mask;
+ }
+
+ static constexpr size_t mask = capacity - 1;
+
+ // Align to avoid false sharing between head_ and tail_
+ alignas(hardware_interference_size) std::atomic<size_t> head_{0};
+ alignas(hardware_interference_size) std::atomic<size_t> tail_{0};
+
+ std::mutex cv_mutex;
+ std::condition_variable_any cv;
+
+ Slot<T>* slots;
+ [[no_unique_address]] std::allocator<Slot<T>> allocator;
+
+ static_assert(std::is_nothrow_copy_assignable_v<T> || std::is_nothrow_move_assignable_v<T>,
+ "T must be nothrow copy or move assignable");
+
+ static_assert(std::is_nothrow_destructible_v<T>, "T must be nothrow destructible");
+};
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+} // namespace Common
diff --git a/src/common/cityhash.cpp b/src/common/cityhash.cpp
index 66218fc21..d50ac9e6c 100644
--- a/src/common/cityhash.cpp
+++ b/src/common/cityhash.cpp
@@ -1,23 +1,8 @@
-// Copyright (c) 2011 Google, Inc.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
+// SPDX-FileCopyrightText: 2011 Google, Inc.
+// SPDX-FileContributor: Geoff Pike
+// SPDX-FileContributor: Jyrki Alakuijala
+// SPDX-License-Identifier: MIT
+
// CityHash, by Geoff Pike and Jyrki Alakuijala
//
// This file provides CityHash64() and related functions.
diff --git a/src/common/cityhash.h b/src/common/cityhash.h
index d74fc7639..627ba81cd 100644
--- a/src/common/cityhash.h
+++ b/src/common/cityhash.h
@@ -1,23 +1,8 @@
-// Copyright (c) 2011 Google, Inc.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
+// SPDX-FileCopyrightText: 2011 Google, Inc.
+// SPDX-FileContributor: Geoff Pike
+// SPDX-FileContributor: Jyrki Alakuijala
+// SPDX-License-Identifier: MIT
+
// CityHash, by Geoff Pike and Jyrki Alakuijala
//
// http://code.google.com/p/cityhash/
diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h
index 4c1e29de6..e1e2a90fc 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -19,14 +18,16 @@
/// Helper macros to insert unused bytes or words to properly align structs. These values will be
/// zero-initialized.
#define INSERT_PADDING_BYTES(num_bytes) \
- std::array<u8, num_bytes> CONCAT2(pad, __LINE__) {}
+ [[maybe_unused]] std::array<u8, num_bytes> CONCAT2(pad, __LINE__) {}
#define INSERT_PADDING_WORDS(num_words) \
- std::array<u32, num_words> CONCAT2(pad, __LINE__) {}
+ [[maybe_unused]] std::array<u32, num_words> CONCAT2(pad, __LINE__) {}
/// These are similar to the INSERT_PADDING_* macros but do not zero-initialize the contents.
/// This keeps the structure trivial to construct.
-#define INSERT_PADDING_BYTES_NOINIT(num_bytes) std::array<u8, num_bytes> CONCAT2(pad, __LINE__)
-#define INSERT_PADDING_WORDS_NOINIT(num_words) std::array<u32, num_words> CONCAT2(pad, __LINE__)
+#define INSERT_PADDING_BYTES_NOINIT(num_bytes) \
+ [[maybe_unused]] std::array<u8, num_bytes> CONCAT2(pad, __LINE__)
+#define INSERT_PADDING_WORDS_NOINIT(num_words) \
+ [[maybe_unused]] std::array<u32, num_words> CONCAT2(pad, __LINE__)
#ifndef _MSC_VER
diff --git a/src/common/common_types.h b/src/common/common_types.h
index 99bffc460..0fc225aff 100644
--- a/src/common/common_types.h
+++ b/src/common/common_types.h
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2012 Gekko Emulator
+// SPDX-FileContributor: ShizZy <shizzy247@gmail.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
/**
* Copyright (C) 2005-2012 Gekko Emulator
*
diff --git a/src/common/concepts.h b/src/common/concepts.h
index aa08065a7..a97555f6a 100644
--- a/src/common/concepts.h
+++ b/src/common/concepts.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp
index c1362631e..da64848da 100644
--- a/src/common/detached_tasks.cpp
+++ b/src/common/detached_tasks.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include "common/assert.h"
@@ -33,9 +32,9 @@ void DetachedTasks::AddTask(std::function<void()> task) {
++instance->count;
std::thread([task{std::move(task)}]() {
task();
- std::unique_lock lock{instance->mutex};
+ std::unique_lock thread_lock{instance->mutex};
--instance->count;
- std::notify_all_at_thread_exit(instance->cv, std::move(lock));
+ std::notify_all_at_thread_exit(instance->cv, std::move(thread_lock));
}).detach();
}
diff --git a/src/common/detached_tasks.h b/src/common/detached_tasks.h
index 5dd8fc27b..416a2d7f3 100644
--- a/src/common/detached_tasks.h
+++ b/src/common/detached_tasks.h
@@ -1,6 +1,5 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/div_ceil.h b/src/common/div_ceil.h
index e1db35464..eebc279c2 100644
--- a/src/common/div_ceil.h
+++ b/src/common/div_ceil.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/dynamic_library.cpp b/src/common/dynamic_library.cpp
index 7f0a10521..054277a2b 100644
--- a/src/common/dynamic_library.cpp
+++ b/src/common/dynamic_library.cpp
@@ -1,8 +1,6 @@
-// Copyright 2019 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2019 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include <cstring>
#include <string>
#include <utility>
diff --git a/src/common/dynamic_library.h b/src/common/dynamic_library.h
index 3512da940..f42bdf441 100644
--- a/src/common/dynamic_library.h
+++ b/src/common/dynamic_library.h
@@ -1,6 +1,5 @@
-// Copyright 2019 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2019 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/elf.h b/src/common/elf.h
new file mode 100644
index 000000000..14a5e9597
--- /dev/null
+++ b/src/common/elf.h
@@ -0,0 +1,333 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+
+#include "common_types.h"
+
+namespace Common {
+namespace ELF {
+
+/* Type for a 16-bit quantity. */
+using Elf32_Half = u16;
+using Elf64_Half = u16;
+
+/* Types for signed and unsigned 32-bit quantities. */
+using Elf32_Word = u32;
+using Elf32_Sword = s32;
+using Elf64_Word = u32;
+using Elf64_Sword = s32;
+
+/* Types for signed and unsigned 64-bit quantities. */
+using Elf32_Xword = u64;
+using Elf32_Sxword = s64;
+using Elf64_Xword = u64;
+using Elf64_Sxword = s64;
+
+/* Type of addresses. */
+using Elf32_Addr = u32;
+using Elf64_Addr = u64;
+
+/* Type of file offsets. */
+using Elf32_Off = u32;
+using Elf64_Off = u64;
+
+/* Type for section indices, which are 16-bit quantities. */
+using Elf32_Section = u16;
+using Elf64_Section = u16;
+
+/* Type for version symbol information. */
+using Elf32_Versym = Elf32_Half;
+using Elf64_Versym = Elf64_Half;
+
+constexpr size_t ElfIdentSize = 16;
+
+/* The ELF file header. This appears at the start of every ELF file. */
+
+struct Elf32_Ehdr {
+ std::array<u8, ElfIdentSize> e_ident; /* Magic number and other info */
+ Elf32_Half e_type; /* Object file type */
+ Elf32_Half e_machine; /* Architecture */
+ Elf32_Word e_version; /* Object file version */
+ Elf32_Addr e_entry; /* Entry point virtual address */
+ Elf32_Off e_phoff; /* Program header table file offset */
+ Elf32_Off e_shoff; /* Section header table file offset */
+ Elf32_Word e_flags; /* Processor-specific flags */
+ Elf32_Half e_ehsize; /* ELF header size in bytes */
+ Elf32_Half e_phentsize; /* Program header table entry size */
+ Elf32_Half e_phnum; /* Program header table entry count */
+ Elf32_Half e_shentsize; /* Section header table entry size */
+ Elf32_Half e_shnum; /* Section header table entry count */
+ Elf32_Half e_shstrndx; /* Section header string table index */
+};
+
+struct Elf64_Ehdr {
+ std::array<u8, ElfIdentSize> e_ident; /* Magic number and other info */
+ Elf64_Half e_type; /* Object file type */
+ Elf64_Half e_machine; /* Architecture */
+ Elf64_Word e_version; /* Object file version */
+ Elf64_Addr e_entry; /* Entry point virtual address */
+ Elf64_Off e_phoff; /* Program header table file offset */
+ Elf64_Off e_shoff; /* Section header table file offset */
+ Elf64_Word e_flags; /* Processor-specific flags */
+ Elf64_Half e_ehsize; /* ELF header size in bytes */
+ Elf64_Half e_phentsize; /* Program header table entry size */
+ Elf64_Half e_phnum; /* Program header table entry count */
+ Elf64_Half e_shentsize; /* Section header table entry size */
+ Elf64_Half e_shnum; /* Section header table entry count */
+ Elf64_Half e_shstrndx; /* Section header string table index */
+};
+
+constexpr u8 ElfClass32 = 1; /* 32-bit objects */
+constexpr u8 ElfClass64 = 2; /* 64-bit objects */
+constexpr u8 ElfData2Lsb = 1; /* 2's complement, little endian */
+constexpr u8 ElfVersionCurrent = 1; /* EV_CURRENT */
+constexpr u8 ElfOsAbiNone = 0; /* System V ABI */
+
+constexpr u16 ElfTypeNone = 0; /* No file type */
+constexpr u16 ElfTypeRel = 0; /* Relocatable file */
+constexpr u16 ElfTypeExec = 0; /* Executable file */
+constexpr u16 ElfTypeDyn = 0; /* Shared object file */
+
+constexpr u16 ElfMachineArm = 40; /* ARM */
+constexpr u16 ElfMachineAArch64 = 183; /* ARM AARCH64 */
+
+constexpr std::array<u8, ElfIdentSize> Elf32Ident{
+ 0x7f, 'E', 'L', 'F', ElfClass32, ElfData2Lsb, ElfVersionCurrent, ElfOsAbiNone};
+
+constexpr std::array<u8, ElfIdentSize> Elf64Ident{
+ 0x7f, 'E', 'L', 'F', ElfClass64, ElfData2Lsb, ElfVersionCurrent, ElfOsAbiNone};
+
+/* Section header. */
+
+struct Elf32_Shdr {
+ Elf32_Word sh_name; /* Section name (string tbl index) */
+ Elf32_Word sh_type; /* Section type */
+ Elf32_Word sh_flags; /* Section flags */
+ Elf32_Addr sh_addr; /* Section virtual addr at execution */
+ Elf32_Off sh_offset; /* Section file offset */
+ Elf32_Word sh_size; /* Section size in bytes */
+ Elf32_Word sh_link; /* Link to another section */
+ Elf32_Word sh_info; /* Additional section information */
+ Elf32_Word sh_addralign; /* Section alignment */
+ Elf32_Word sh_entsize; /* Entry size if section holds table */
+};
+
+struct Elf64_Shdr {
+ Elf64_Word sh_name; /* Section name (string tbl index) */
+ Elf64_Word sh_type; /* Section type */
+ Elf64_Xword sh_flags; /* Section flags */
+ Elf64_Addr sh_addr; /* Section virtual addr at execution */
+ Elf64_Off sh_offset; /* Section file offset */
+ Elf64_Xword sh_size; /* Section size in bytes */
+ Elf64_Word sh_link; /* Link to another section */
+ Elf64_Word sh_info; /* Additional section information */
+ Elf64_Xword sh_addralign; /* Section alignment */
+ Elf64_Xword sh_entsize; /* Entry size if section holds table */
+};
+
+constexpr u32 ElfShnUndef = 0; /* Undefined section */
+
+constexpr u32 ElfShtNull = 0; /* Section header table entry unused */
+constexpr u32 ElfShtProgBits = 1; /* Program data */
+constexpr u32 ElfShtSymtab = 2; /* Symbol table */
+constexpr u32 ElfShtStrtab = 3; /* String table */
+constexpr u32 ElfShtRela = 4; /* Relocation entries with addends */
+constexpr u32 ElfShtDynamic = 6; /* Dynamic linking information */
+constexpr u32 ElfShtNobits = 7; /* Program space with no data (bss) */
+constexpr u32 ElfShtRel = 9; /* Relocation entries, no addends */
+constexpr u32 ElfShtDynsym = 11; /* Dynamic linker symbol table */
+
+/* Symbol table entry. */
+
+struct Elf32_Sym {
+ Elf32_Word st_name; /* Symbol name (string tbl index) */
+ Elf32_Addr st_value; /* Symbol value */
+ Elf32_Word st_size; /* Symbol size */
+ u8 st_info; /* Symbol type and binding */
+ u8 st_other; /* Symbol visibility */
+ Elf32_Section st_shndx; /* Section index */
+};
+
+struct Elf64_Sym {
+ Elf64_Word st_name; /* Symbol name (string tbl index) */
+ u8 st_info; /* Symbol type and binding */
+ u8 st_other; /* Symbol visibility */
+ Elf64_Section st_shndx; /* Section index */
+ Elf64_Addr st_value; /* Symbol value */
+ Elf64_Xword st_size; /* Symbol size */
+};
+
+/* How to extract and insert information held in the st_info field. */
+
+static inline u8 ElfStBind(u8 st_info) {
+ return st_info >> 4;
+}
+static inline u8 ElfStType(u8 st_info) {
+ return st_info & 0xf;
+}
+static inline u8 ElfStInfo(u8 st_bind, u8 st_type) {
+ return static_cast<u8>((st_bind << 4) + (st_type & 0xf));
+}
+
+constexpr u8 ElfBindLocal = 0; /* Local symbol */
+constexpr u8 ElfBindGlobal = 1; /* Global symbol */
+constexpr u8 ElfBindWeak = 2; /* Weak symbol */
+
+constexpr u8 ElfTypeUnspec = 0; /* Symbol type is unspecified */
+constexpr u8 ElfTypeObject = 1; /* Symbol is a data object */
+constexpr u8 ElfTypeFunc = 2; /* Symbol is a code object */
+
+static inline u8 ElfStVisibility(u8 st_other) {
+ return static_cast<u8>(st_other & 0x3);
+}
+
+constexpr u8 ElfVisibilityDefault = 0; /* Default symbol visibility rules */
+constexpr u8 ElfVisibilityInternal = 1; /* Processor specific hidden class */
+constexpr u8 ElfVisibilityHidden = 2; /* Sym unavailable in other modules */
+constexpr u8 ElfVisibilityProtected = 3; /* Not preemptible, not exported */
+
+/* Relocation table entry without addend (in section of type ShtRel). */
+
+struct Elf32_Rel {
+ Elf32_Addr r_offset; /* Address */
+ Elf32_Word r_info; /* Relocation type and symbol index */
+};
+
+/* Relocation table entry with addend (in section of type ShtRela). */
+
+struct Elf32_Rela {
+ Elf32_Addr r_offset; /* Address */
+ Elf32_Word r_info; /* Relocation type and symbol index */
+ Elf32_Sword r_addend; /* Addend */
+};
+
+struct Elf64_Rela {
+ Elf64_Addr r_offset; /* Address */
+ Elf64_Xword r_info; /* Relocation type and symbol index */
+ Elf64_Sxword r_addend; /* Addend */
+};
+
+/* How to extract and insert information held in the r_info field. */
+
+static inline u32 Elf32RelSymIndex(Elf32_Word r_info) {
+ return r_info >> 8;
+}
+static inline u8 Elf32RelType(Elf32_Word r_info) {
+ return static_cast<u8>(r_info & 0xff);
+}
+static inline Elf32_Word Elf32RelInfo(u32 sym_index, u8 type) {
+ return (sym_index << 8) + type;
+}
+static inline u32 Elf64RelSymIndex(Elf64_Xword r_info) {
+ return static_cast<u32>(r_info >> 32);
+}
+static inline u32 Elf64RelType(Elf64_Xword r_info) {
+ return r_info & 0xffffffff;
+}
+static inline Elf64_Xword Elf64RelInfo(u32 sym_index, u32 type) {
+ return (static_cast<Elf64_Xword>(sym_index) << 32) + type;
+}
+
+constexpr u32 ElfArmCopy = 20; /* Copy symbol at runtime */
+constexpr u32 ElfArmGlobDat = 21; /* Create GOT entry */
+constexpr u32 ElfArmJumpSlot = 22; /* Create PLT entry */
+constexpr u32 ElfArmRelative = 23; /* Adjust by program base */
+
+constexpr u32 ElfAArch64Copy = 1024; /* Copy symbol at runtime */
+constexpr u32 ElfAArch64GlobDat = 1025; /* Create GOT entry */
+constexpr u32 ElfAArch64JumpSlot = 1026; /* Create PLT entry */
+constexpr u32 ElfAArch64Relative = 1027; /* Adjust by program base */
+
+/* Program segment header. */
+
+struct Elf32_Phdr {
+ Elf32_Word p_type; /* Segment type */
+ Elf32_Off p_offset; /* Segment file offset */
+ Elf32_Addr p_vaddr; /* Segment virtual address */
+ Elf32_Addr p_paddr; /* Segment physical address */
+ Elf32_Word p_filesz; /* Segment size in file */
+ Elf32_Word p_memsz; /* Segment size in memory */
+ Elf32_Word p_flags; /* Segment flags */
+ Elf32_Word p_align; /* Segment alignment */
+};
+
+struct Elf64_Phdr {
+ Elf64_Word p_type; /* Segment type */
+ Elf64_Word p_flags; /* Segment flags */
+ Elf64_Off p_offset; /* Segment file offset */
+ Elf64_Addr p_vaddr; /* Segment virtual address */
+ Elf64_Addr p_paddr; /* Segment physical address */
+ Elf64_Xword p_filesz; /* Segment size in file */
+ Elf64_Xword p_memsz; /* Segment size in memory */
+ Elf64_Xword p_align; /* Segment alignment */
+};
+
+/* Legal values for p_type (segment type). */
+
+constexpr u32 ElfPtNull = 0; /* Program header table entry unused */
+constexpr u32 ElfPtLoad = 1; /* Loadable program segment */
+constexpr u32 ElfPtDynamic = 2; /* Dynamic linking information */
+constexpr u32 ElfPtInterp = 3; /* Program interpreter */
+constexpr u32 ElfPtNote = 4; /* Auxiliary information */
+constexpr u32 ElfPtPhdr = 6; /* Entry for header table itself */
+constexpr u32 ElfPtTls = 7; /* Thread-local storage segment */
+
+/* Legal values for p_flags (segment flags). */
+
+constexpr u32 ElfPfExec = 0; /* Segment is executable */
+constexpr u32 ElfPfWrite = 1; /* Segment is writable */
+constexpr u32 ElfPfRead = 2; /* Segment is readable */
+
+/* Dynamic section entry. */
+
+struct Elf32_Dyn {
+ Elf32_Sword d_tag; /* Dynamic entry type */
+ union {
+ Elf32_Word d_val; /* Integer value */
+ Elf32_Addr d_ptr; /* Address value */
+ } d_un;
+};
+
+struct Elf64_Dyn {
+ Elf64_Sxword d_tag; /* Dynamic entry type */
+ union {
+ Elf64_Xword d_val; /* Integer value */
+ Elf64_Addr d_ptr; /* Address value */
+ } d_un;
+};
+
+/* Legal values for d_tag (dynamic entry type). */
+
+constexpr u32 ElfDtNull = 0; /* Marks end of dynamic section */
+constexpr u32 ElfDtNeeded = 1; /* Name of needed library */
+constexpr u32 ElfDtPltRelSz = 2; /* Size in bytes of PLT relocs */
+constexpr u32 ElfDtPltGot = 3; /* Processor defined value */
+constexpr u32 ElfDtHash = 4; /* Address of symbol hash table */
+constexpr u32 ElfDtStrtab = 5; /* Address of string table */
+constexpr u32 ElfDtSymtab = 6; /* Address of symbol table */
+constexpr u32 ElfDtRela = 7; /* Address of Rela relocs */
+constexpr u32 ElfDtRelasz = 8; /* Total size of Rela relocs */
+constexpr u32 ElfDtRelaent = 9; /* Size of one Rela reloc */
+constexpr u32 ElfDtStrsz = 10; /* Size of string table */
+constexpr u32 ElfDtSyment = 11; /* Size of one symbol table entry */
+constexpr u32 ElfDtInit = 12; /* Address of init function */
+constexpr u32 ElfDtFini = 13; /* Address of termination function */
+constexpr u32 ElfDtRel = 17; /* Address of Rel relocs */
+constexpr u32 ElfDtRelsz = 18; /* Total size of Rel relocs */
+constexpr u32 ElfDtRelent = 19; /* Size of one Rel reloc */
+constexpr u32 ElfDtPltRel = 20; /* Type of reloc in PLT */
+constexpr u32 ElfDtTextRel = 22; /* Reloc might modify .text */
+constexpr u32 ElfDtJmpRel = 23; /* Address of PLT relocs */
+constexpr u32 ElfDtBindNow = 24; /* Process relocations of object */
+constexpr u32 ElfDtInitArray = 25; /* Array with addresses of init fct */
+constexpr u32 ElfDtFiniArray = 26; /* Array with addresses of fini fct */
+constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */
+constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */
+constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */
+
+} // namespace ELF
+} // namespace Common
diff --git a/src/common/error.cpp b/src/common/error.cpp
index d4455e310..ddb03bd45 100644
--- a/src/common/error.cpp
+++ b/src/common/error.cpp
@@ -1,6 +1,6 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstddef>
#ifdef _WIN32
diff --git a/src/common/error.h b/src/common/error.h
index e084d4b0f..62a3bd835 100644
--- a/src/common/error.h
+++ b/src/common/error.h
@@ -1,6 +1,6 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/expected.h b/src/common/expected.h
index c8d8579c1..6e6c86ee7 100644
--- a/src/common/expected.h
+++ b/src/common/expected.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
// This is based on the proposed implementation of std::expected (P0323)
// https://github.com/TartanLlama/expected/blob/master/include/tl/expected.hpp
diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp
index 81b212e4b..bc92b360b 100644
--- a/src/common/fiber.cpp
+++ b/src/common/fiber.cpp
@@ -1,10 +1,10 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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,11 +19,9 @@ struct Fiber::FiberImpl {
VirtualBuffer<u8> stack;
VirtualBuffer<u8> rewind_stack;
- SpinLock guard{};
- std::function<void(void*)> entry_point;
- std::function<void(void*)> rewind_point;
- void* rewind_parameter{};
- void* start_parameter{};
+ std::mutex guard;
+ std::function<void()> entry_point;
+ std::function<void()> rewind_point;
std::shared_ptr<Fiber> previous_fiber;
bool is_thread_fiber{};
bool released{};
@@ -34,13 +32,8 @@ struct Fiber::FiberImpl {
boost::context::detail::fcontext_t rewind_context{};
};
-void Fiber::SetStartParameter(void* new_parameter) {
- impl->start_parameter = new_parameter;
-}
-
-void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* rewind_param) {
+void Fiber::SetRewindPoint(std::function<void()>&& rewind_func) {
impl->rewind_point = std::move(rewind_func);
- impl->rewind_parameter = rewind_param;
}
void Fiber::Start(boost::context::detail::transfer_t& transfer) {
@@ -48,7 +41,7 @@ void Fiber::Start(boost::context::detail::transfer_t& transfer) {
impl->previous_fiber->impl->context = transfer.fctx;
impl->previous_fiber->impl->guard.unlock();
impl->previous_fiber.reset();
- impl->entry_point(impl->start_parameter);
+ impl->entry_point();
UNREACHABLE();
}
@@ -59,7 +52,7 @@ void Fiber::OnRewind([[maybe_unused]] boost::context::detail::transfer_t& transf
u8* tmp = impl->stack_limit;
impl->stack_limit = impl->rewind_stack_limit;
impl->rewind_stack_limit = tmp;
- impl->rewind_point(impl->rewind_parameter);
+ impl->rewind_point();
UNREACHABLE();
}
@@ -73,10 +66,8 @@ void Fiber::RewindStartFunc(boost::context::detail::transfer_t transfer) {
fiber->OnRewind(transfer);
}
-Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter)
- : impl{std::make_unique<FiberImpl>()} {
+Fiber::Fiber(std::function<void()>&& entry_point_func) : impl{std::make_unique<FiberImpl>()} {
impl->entry_point = std::move(entry_point_func);
- impl->start_parameter = start_parameter;
impl->stack_limit = impl->stack.data();
impl->rewind_stack_limit = impl->rewind_stack.data();
u8* stack_base = impl->stack_limit + default_stack_size;
diff --git a/src/common/fiber.h b/src/common/fiber.h
index f2a8ff29a..f24d333a3 100644
--- a/src/common/fiber.h
+++ b/src/common/fiber.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -30,7 +29,7 @@ namespace Common {
*/
class Fiber {
public:
- Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter);
+ Fiber(std::function<void()>&& entry_point_func);
~Fiber();
Fiber(const Fiber&) = delete;
@@ -44,16 +43,13 @@ public:
static void YieldTo(std::weak_ptr<Fiber> weak_from, Fiber& to);
[[nodiscard]] static std::shared_ptr<Fiber> ThreadToFiber();
- void SetRewindPoint(std::function<void(void*)>&& rewind_func, void* rewind_param);
+ void SetRewindPoint(std::function<void()>&& rewind_func);
void Rewind();
/// Only call from main thread's fiber
void Exit();
- /// Changes the start parameter of the fiber. Has no effect if the fiber already started
- void SetStartParameter(void* new_parameter);
-
private:
Fiber();
diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h
new file mode 100644
index 000000000..4a0f72cc9
--- /dev/null
+++ b/src/common/fixed_point.h
@@ -0,0 +1,706 @@
+// SPDX-FileCopyrightText: 2015 Evan Teran
+// SPDX-License-Identifier: MIT
+
+// From: https://github.com/eteran/cpp-utilities/blob/master/fixed/include/cpp-utilities/fixed.h
+// See also: http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math
+
+#ifndef FIXED_H_
+#define FIXED_H_
+
+#if __cplusplus >= 201402L
+#define CONSTEXPR14 constexpr
+#else
+#define CONSTEXPR14
+#endif
+
+#include <cstddef> // for size_t
+#include <cstdint>
+#include <exception>
+#include <ostream>
+#include <type_traits>
+
+namespace Common {
+
+template <size_t I, size_t F>
+class FixedPoint;
+
+namespace detail {
+
+// helper templates to make magic with types :)
+// these allow us to determine resonable types from
+// a desired size, they also let us infer the next largest type
+// from a type which is nice for the division op
+template <size_t T>
+struct type_from_size {
+ using value_type = void;
+ using unsigned_type = void;
+ using signed_type = void;
+ static constexpr bool is_specialized = false;
+};
+
+#if defined(__GNUC__) && defined(__x86_64__) && !defined(__STRICT_ANSI__)
+template <>
+struct type_from_size<128> {
+ static constexpr bool is_specialized = true;
+ static constexpr size_t size = 128;
+
+ using value_type = __int128;
+ using unsigned_type = unsigned __int128;
+ using signed_type = __int128;
+ using next_size = type_from_size<256>;
+};
+#endif
+
+template <>
+struct type_from_size<64> {
+ static constexpr bool is_specialized = true;
+ static constexpr size_t size = 64;
+
+ using value_type = int64_t;
+ using unsigned_type = std::make_unsigned<value_type>::type;
+ using signed_type = std::make_signed<value_type>::type;
+ using next_size = type_from_size<128>;
+};
+
+template <>
+struct type_from_size<32> {
+ static constexpr bool is_specialized = true;
+ static constexpr size_t size = 32;
+
+ using value_type = int32_t;
+ using unsigned_type = std::make_unsigned<value_type>::type;
+ using signed_type = std::make_signed<value_type>::type;
+ using next_size = type_from_size<64>;
+};
+
+template <>
+struct type_from_size<16> {
+ static constexpr bool is_specialized = true;
+ static constexpr size_t size = 16;
+
+ using value_type = int16_t;
+ using unsigned_type = std::make_unsigned<value_type>::type;
+ using signed_type = std::make_signed<value_type>::type;
+ using next_size = type_from_size<32>;
+};
+
+template <>
+struct type_from_size<8> {
+ static constexpr bool is_specialized = true;
+ static constexpr size_t size = 8;
+
+ using value_type = int8_t;
+ using unsigned_type = std::make_unsigned<value_type>::type;
+ using signed_type = std::make_signed<value_type>::type;
+ using next_size = type_from_size<16>;
+};
+
+// this is to assist in adding support for non-native base
+// types (for adding big-int support), this should be fine
+// unless your bit-int class doesn't nicely support casting
+template <class B, class N>
+constexpr B next_to_base(N rhs) {
+ return static_cast<B>(rhs);
+}
+
+struct divide_by_zero : std::exception {};
+
+template <size_t I, size_t F>
+CONSTEXPR14 FixedPoint<I, F> divide(
+ FixedPoint<I, F> numerator, FixedPoint<I, F> denominator, FixedPoint<I, F>& remainder,
+ typename std::enable_if<type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) {
+
+ using next_type = typename FixedPoint<I, F>::next_type;
+ using base_type = typename FixedPoint<I, F>::base_type;
+ constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits;
+
+ next_type t(numerator.to_raw());
+ t <<= fractional_bits;
+
+ FixedPoint<I, F> quotient;
+
+ quotient = FixedPoint<I, F>::from_base(next_to_base<base_type>(t / denominator.to_raw()));
+ remainder = FixedPoint<I, F>::from_base(next_to_base<base_type>(t % denominator.to_raw()));
+
+ return quotient;
+}
+
+template <size_t I, size_t F>
+CONSTEXPR14 FixedPoint<I, F> divide(
+ FixedPoint<I, F> numerator, FixedPoint<I, F> denominator, FixedPoint<I, F>& remainder,
+ typename std::enable_if<!type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) {
+
+ using unsigned_type = typename FixedPoint<I, F>::unsigned_type;
+
+ constexpr int bits = FixedPoint<I, F>::total_bits;
+
+ if (denominator == 0) {
+ throw divide_by_zero();
+ } else {
+
+ int sign = 0;
+
+ FixedPoint<I, F> quotient;
+
+ if (numerator < 0) {
+ sign ^= 1;
+ numerator = -numerator;
+ }
+
+ if (denominator < 0) {
+ sign ^= 1;
+ denominator = -denominator;
+ }
+
+ unsigned_type n = numerator.to_raw();
+ unsigned_type d = denominator.to_raw();
+ unsigned_type x = 1;
+ unsigned_type answer = 0;
+
+ // egyptian division algorithm
+ while ((n >= d) && (((d >> (bits - 1)) & 1) == 0)) {
+ x <<= 1;
+ d <<= 1;
+ }
+
+ while (x != 0) {
+ if (n >= d) {
+ n -= d;
+ answer += x;
+ }
+
+ x >>= 1;
+ d >>= 1;
+ }
+
+ unsigned_type l1 = n;
+ unsigned_type l2 = denominator.to_raw();
+
+ // calculate the lower bits (needs to be unsigned)
+ while (l1 >> (bits - F) > 0) {
+ l1 >>= 1;
+ l2 >>= 1;
+ }
+ const unsigned_type lo = (l1 << F) / l2;
+
+ quotient = FixedPoint<I, F>::from_base((answer << F) | lo);
+ remainder = n;
+
+ if (sign) {
+ quotient = -quotient;
+ }
+
+ return quotient;
+ }
+}
+
+// this is the usual implementation of multiplication
+template <size_t I, size_t F>
+CONSTEXPR14 FixedPoint<I, F> multiply(
+ FixedPoint<I, F> lhs, FixedPoint<I, F> rhs,
+ typename std::enable_if<type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) {
+
+ using next_type = typename FixedPoint<I, F>::next_type;
+ using base_type = typename FixedPoint<I, F>::base_type;
+
+ constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits;
+
+ next_type t(static_cast<next_type>(lhs.to_raw()) * static_cast<next_type>(rhs.to_raw()));
+ t >>= fractional_bits;
+
+ return FixedPoint<I, F>::from_base(next_to_base<base_type>(t));
+}
+
+// this is the fall back version we use when we don't have a next size
+// it is slightly slower, but is more robust since it doesn't
+// require and upgraded type
+template <size_t I, size_t F>
+CONSTEXPR14 FixedPoint<I, F> multiply(
+ FixedPoint<I, F> lhs, FixedPoint<I, F> rhs,
+ typename std::enable_if<!type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) {
+
+ using base_type = typename FixedPoint<I, F>::base_type;
+
+ constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits;
+ constexpr base_type integer_mask = FixedPoint<I, F>::integer_mask;
+ constexpr base_type fractional_mask = FixedPoint<I, F>::fractional_mask;
+
+ // more costly but doesn't need a larger type
+ const base_type a_hi = (lhs.to_raw() & integer_mask) >> fractional_bits;
+ const base_type b_hi = (rhs.to_raw() & integer_mask) >> fractional_bits;
+ const base_type a_lo = (lhs.to_raw() & fractional_mask);
+ const base_type b_lo = (rhs.to_raw() & fractional_mask);
+
+ const base_type x1 = a_hi * b_hi;
+ const base_type x2 = a_hi * b_lo;
+ const base_type x3 = a_lo * b_hi;
+ const base_type x4 = a_lo * b_lo;
+
+ return FixedPoint<I, F>::from_base((x1 << fractional_bits) + (x3 + x2) +
+ (x4 >> fractional_bits));
+}
+} // namespace detail
+
+template <size_t I, size_t F>
+class FixedPoint {
+ static_assert(detail::type_from_size<I + F>::is_specialized, "invalid combination of sizes");
+
+public:
+ static constexpr size_t fractional_bits = F;
+ static constexpr size_t integer_bits = I;
+ static constexpr size_t total_bits = I + F;
+
+ using base_type_info = detail::type_from_size<total_bits>;
+
+ using base_type = typename base_type_info::value_type;
+ using next_type = typename base_type_info::next_size::value_type;
+ using unsigned_type = typename base_type_info::unsigned_type;
+
+public:
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Woverflow"
+#endif
+ static constexpr base_type fractional_mask =
+ ~(static_cast<unsigned_type>(~base_type(0)) << fractional_bits);
+ static constexpr base_type integer_mask = ~fractional_mask;
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+public:
+ static constexpr base_type one = base_type(1) << fractional_bits;
+
+public: // constructors
+ FixedPoint() = default;
+ FixedPoint(const FixedPoint&) = default;
+ FixedPoint(FixedPoint&&) = default;
+ FixedPoint& operator=(const FixedPoint&) = default;
+
+ template <class Number>
+ constexpr FixedPoint(
+ Number n, typename std::enable_if<std::is_arithmetic<Number>::value>::type* = nullptr)
+ : data_(static_cast<base_type>(n * one)) {}
+
+public: // conversion
+ template <size_t I2, size_t F2>
+ CONSTEXPR14 explicit FixedPoint(FixedPoint<I2, F2> other) {
+ static_assert(I2 <= I && F2 <= F, "Scaling conversion can only upgrade types");
+ using T = FixedPoint<I2, F2>;
+
+ const base_type fractional = (other.data_ & T::fractional_mask);
+ const base_type integer = (other.data_ & T::integer_mask) >> T::fractional_bits;
+ data_ =
+ (integer << fractional_bits) | (fractional << (fractional_bits - T::fractional_bits));
+ }
+
+private:
+ // this makes it simpler to create a FixedPoint point object from
+ // a native type without scaling
+ // use "FixedPoint::from_base" in order to perform this.
+ struct NoScale {};
+
+ constexpr FixedPoint(base_type n, const NoScale&) : data_(n) {}
+
+public:
+ static constexpr FixedPoint from_base(base_type n) {
+ return FixedPoint(n, NoScale());
+ }
+
+public: // comparison operators
+ constexpr bool operator==(FixedPoint rhs) const {
+ return data_ == rhs.data_;
+ }
+
+ constexpr bool operator!=(FixedPoint rhs) const {
+ return data_ != rhs.data_;
+ }
+
+ constexpr bool operator<(FixedPoint rhs) const {
+ return data_ < rhs.data_;
+ }
+
+ constexpr bool operator>(FixedPoint rhs) const {
+ return data_ > rhs.data_;
+ }
+
+ constexpr bool operator<=(FixedPoint rhs) const {
+ return data_ <= rhs.data_;
+ }
+
+ constexpr bool operator>=(FixedPoint rhs) const {
+ return data_ >= rhs.data_;
+ }
+
+public: // unary operators
+ constexpr bool operator!() const {
+ return !data_;
+ }
+
+ constexpr FixedPoint operator~() const {
+ // NOTE(eteran): this will often appear to "just negate" the value
+ // that is not an error, it is because -x == (~x+1)
+ // and that "+1" is adding an infinitesimally small fraction to the
+ // complimented value
+ return FixedPoint::from_base(~data_);
+ }
+
+ constexpr FixedPoint operator-() const {
+ return FixedPoint::from_base(-data_);
+ }
+
+ constexpr FixedPoint operator+() const {
+ return FixedPoint::from_base(+data_);
+ }
+
+ CONSTEXPR14 FixedPoint& operator++() {
+ data_ += one;
+ return *this;
+ }
+
+ CONSTEXPR14 FixedPoint& operator--() {
+ data_ -= one;
+ return *this;
+ }
+
+ CONSTEXPR14 FixedPoint operator++(int) {
+ FixedPoint tmp(*this);
+ data_ += one;
+ return tmp;
+ }
+
+ CONSTEXPR14 FixedPoint operator--(int) {
+ FixedPoint tmp(*this);
+ data_ -= one;
+ return tmp;
+ }
+
+public: // basic math operators
+ CONSTEXPR14 FixedPoint& operator+=(FixedPoint n) {
+ data_ += n.data_;
+ return *this;
+ }
+
+ CONSTEXPR14 FixedPoint& operator-=(FixedPoint n) {
+ data_ -= n.data_;
+ return *this;
+ }
+
+ CONSTEXPR14 FixedPoint& operator*=(FixedPoint n) {
+ return assign(detail::multiply(*this, n));
+ }
+
+ CONSTEXPR14 FixedPoint& operator/=(FixedPoint n) {
+ FixedPoint temp;
+ return assign(detail::divide(*this, n, temp));
+ }
+
+private:
+ CONSTEXPR14 FixedPoint& assign(FixedPoint rhs) {
+ data_ = rhs.data_;
+ return *this;
+ }
+
+public: // binary math operators, effects underlying bit pattern since these
+ // don't really typically make sense for non-integer values
+ CONSTEXPR14 FixedPoint& operator&=(FixedPoint n) {
+ data_ &= n.data_;
+ return *this;
+ }
+
+ CONSTEXPR14 FixedPoint& operator|=(FixedPoint n) {
+ data_ |= n.data_;
+ return *this;
+ }
+
+ CONSTEXPR14 FixedPoint& operator^=(FixedPoint n) {
+ data_ ^= n.data_;
+ return *this;
+ }
+
+ template <class Integer,
+ class = typename std::enable_if<std::is_integral<Integer>::value>::type>
+ CONSTEXPR14 FixedPoint& operator>>=(Integer n) {
+ data_ >>= n;
+ return *this;
+ }
+
+ template <class Integer,
+ class = typename std::enable_if<std::is_integral<Integer>::value>::type>
+ CONSTEXPR14 FixedPoint& operator<<=(Integer n) {
+ data_ <<= n;
+ return *this;
+ }
+
+public: // conversion to basic types
+ constexpr void round_up() {
+ data_ += (data_ & fractional_mask) >> 1;
+ }
+
+ constexpr int to_int() {
+ round_up();
+ return static_cast<int>((data_ & integer_mask) >> fractional_bits);
+ }
+
+ constexpr unsigned int to_uint() const {
+ round_up();
+ return static_cast<unsigned int>((data_ & integer_mask) >> fractional_bits);
+ }
+
+ constexpr int64_t to_long() {
+ round_up();
+ return static_cast<int64_t>((data_ & integer_mask) >> fractional_bits);
+ }
+
+ constexpr int to_int_floor() const {
+ return static_cast<int>((data_ & integer_mask) >> fractional_bits);
+ }
+
+ constexpr int64_t to_long_floor() {
+ return static_cast<int64_t>((data_ & integer_mask) >> fractional_bits);
+ }
+
+ constexpr unsigned int to_uint_floor() const {
+ return static_cast<unsigned int>((data_ & integer_mask) >> fractional_bits);
+ }
+
+ constexpr float to_float() const {
+ return static_cast<float>(data_) / FixedPoint::one;
+ }
+
+ constexpr double to_double() const {
+ return static_cast<double>(data_) / FixedPoint::one;
+ }
+
+ constexpr base_type to_raw() const {
+ return data_;
+ }
+
+ constexpr void clear_int() {
+ data_ &= fractional_mask;
+ }
+
+ constexpr base_type get_frac() const {
+ return data_ & fractional_mask;
+ }
+
+public:
+ CONSTEXPR14 void swap(FixedPoint& rhs) {
+ using std::swap;
+ swap(data_, rhs.data_);
+ }
+
+public:
+ base_type data_;
+};
+
+// if we have the same fractional portion, but differing integer portions, we trivially upgrade the
+// smaller type
+template <size_t I1, size_t I2, size_t F>
+CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type
+operator+(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) {
+
+ using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type;
+
+ const T l = T::from_base(lhs.to_raw());
+ const T r = T::from_base(rhs.to_raw());
+ return l + r;
+}
+
+template <size_t I1, size_t I2, size_t F>
+CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type
+operator-(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) {
+
+ using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type;
+
+ const T l = T::from_base(lhs.to_raw());
+ const T r = T::from_base(rhs.to_raw());
+ return l - r;
+}
+
+template <size_t I1, size_t I2, size_t F>
+CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type
+operator*(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) {
+
+ using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type;
+
+ const T l = T::from_base(lhs.to_raw());
+ const T r = T::from_base(rhs.to_raw());
+ return l * r;
+}
+
+template <size_t I1, size_t I2, size_t F>
+CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type
+operator/(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) {
+
+ using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type;
+
+ const T l = T::from_base(lhs.to_raw());
+ const T r = T::from_base(rhs.to_raw());
+ return l / r;
+}
+
+template <size_t I, size_t F>
+std::ostream& operator<<(std::ostream& os, FixedPoint<I, F> f) {
+ os << f.to_double();
+ return os;
+}
+
+// basic math operators
+template <size_t I, size_t F>
+CONSTEXPR14 FixedPoint<I, F> operator+(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) {
+ lhs += rhs;
+ return lhs;
+}
+template <size_t I, size_t F>
+CONSTEXPR14 FixedPoint<I, F> operator-(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) {
+ lhs -= rhs;
+ return lhs;
+}
+template <size_t I, size_t F>
+CONSTEXPR14 FixedPoint<I, F> operator*(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) {
+ lhs *= rhs;
+ return lhs;
+}
+template <size_t I, size_t F>
+CONSTEXPR14 FixedPoint<I, F> operator/(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) {
+ lhs /= rhs;
+ return lhs;
+}
+
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+CONSTEXPR14 FixedPoint<I, F> operator+(FixedPoint<I, F> lhs, Number rhs) {
+ lhs += FixedPoint<I, F>(rhs);
+ return lhs;
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+CONSTEXPR14 FixedPoint<I, F> operator-(FixedPoint<I, F> lhs, Number rhs) {
+ lhs -= FixedPoint<I, F>(rhs);
+ return lhs;
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+CONSTEXPR14 FixedPoint<I, F> operator*(FixedPoint<I, F> lhs, Number rhs) {
+ lhs *= FixedPoint<I, F>(rhs);
+ return lhs;
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+CONSTEXPR14 FixedPoint<I, F> operator/(FixedPoint<I, F> lhs, Number rhs) {
+ lhs /= FixedPoint<I, F>(rhs);
+ return lhs;
+}
+
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+CONSTEXPR14 FixedPoint<I, F> operator+(Number lhs, FixedPoint<I, F> rhs) {
+ FixedPoint<I, F> tmp(lhs);
+ tmp += rhs;
+ return tmp;
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+CONSTEXPR14 FixedPoint<I, F> operator-(Number lhs, FixedPoint<I, F> rhs) {
+ FixedPoint<I, F> tmp(lhs);
+ tmp -= rhs;
+ return tmp;
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+CONSTEXPR14 FixedPoint<I, F> operator*(Number lhs, FixedPoint<I, F> rhs) {
+ FixedPoint<I, F> tmp(lhs);
+ tmp *= rhs;
+ return tmp;
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+CONSTEXPR14 FixedPoint<I, F> operator/(Number lhs, FixedPoint<I, F> rhs) {
+ FixedPoint<I, F> tmp(lhs);
+ tmp /= rhs;
+ return tmp;
+}
+
+// shift operators
+template <size_t I, size_t F, class Integer,
+ class = typename std::enable_if<std::is_integral<Integer>::value>::type>
+CONSTEXPR14 FixedPoint<I, F> operator<<(FixedPoint<I, F> lhs, Integer rhs) {
+ lhs <<= rhs;
+ return lhs;
+}
+template <size_t I, size_t F, class Integer,
+ class = typename std::enable_if<std::is_integral<Integer>::value>::type>
+CONSTEXPR14 FixedPoint<I, F> operator>>(FixedPoint<I, F> lhs, Integer rhs) {
+ lhs >>= rhs;
+ return lhs;
+}
+
+// comparison operators
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+constexpr bool operator>(FixedPoint<I, F> lhs, Number rhs) {
+ return lhs > FixedPoint<I, F>(rhs);
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+constexpr bool operator<(FixedPoint<I, F> lhs, Number rhs) {
+ return lhs < FixedPoint<I, F>(rhs);
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+constexpr bool operator>=(FixedPoint<I, F> lhs, Number rhs) {
+ return lhs >= FixedPoint<I, F>(rhs);
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+constexpr bool operator<=(FixedPoint<I, F> lhs, Number rhs) {
+ return lhs <= FixedPoint<I, F>(rhs);
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+constexpr bool operator==(FixedPoint<I, F> lhs, Number rhs) {
+ return lhs == FixedPoint<I, F>(rhs);
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+constexpr bool operator!=(FixedPoint<I, F> lhs, Number rhs) {
+ return lhs != FixedPoint<I, F>(rhs);
+}
+
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+constexpr bool operator>(Number lhs, FixedPoint<I, F> rhs) {
+ return FixedPoint<I, F>(lhs) > rhs;
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+constexpr bool operator<(Number lhs, FixedPoint<I, F> rhs) {
+ return FixedPoint<I, F>(lhs) < rhs;
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+constexpr bool operator>=(Number lhs, FixedPoint<I, F> rhs) {
+ return FixedPoint<I, F>(lhs) >= rhs;
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+constexpr bool operator<=(Number lhs, FixedPoint<I, F> rhs) {
+ return FixedPoint<I, F>(lhs) <= rhs;
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+constexpr bool operator==(Number lhs, FixedPoint<I, F> rhs) {
+ return FixedPoint<I, F>(lhs) == rhs;
+}
+template <size_t I, size_t F, class Number,
+ class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
+constexpr bool operator!=(Number lhs, FixedPoint<I, F> rhs) {
+ return FixedPoint<I, F>(lhs) != rhs;
+}
+
+} // namespace Common
+
+#undef CONSTEXPR14
+
+#endif
diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp
index 274f57659..fa8422c41 100644
--- a/src/common/fs/file.cpp
+++ b/src/common/fs/file.cpp
@@ -1,10 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/fs/file.h"
#include "common/fs/fs.h"
-#include "common/fs/path_util.h"
#include "common/logging/log.h"
#ifdef _WIN32
diff --git a/src/common/fs/file.h b/src/common/fs/file.h
index a4f7944cd..69b53384c 100644
--- a/src/common/fs/file.h
+++ b/src/common/fs/file.h
@@ -1,15 +1,12 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdio>
#include <filesystem>
-#include <fstream>
#include <span>
#include <type_traits>
-#include <vector>
#include "common/concepts.h"
#include "common/fs/fs_types.h"
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp
index 9089cad67..e1716c62d 100644
--- a/src/common/fs/fs.cpp
+++ b/src/common/fs/fs.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/fs/file.h"
#include "common/fs/fs.h"
diff --git a/src/common/fs/fs.h b/src/common/fs/fs.h
index 183126de3..ce3eb309a 100644
--- a/src/common/fs/fs.h
+++ b/src/common/fs/fs.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h
index 5d447f108..c77c112f1 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/fs/fs_types.h b/src/common/fs/fs_types.h
index 089980aee..5a4090c19 100644
--- a/src/common/fs/fs_types.h
+++ b/src/common/fs/fs_types.h
@@ -1,13 +1,11 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <functional>
#include "common/common_funcs.h"
-#include "common/common_types.h"
namespace Common::FS {
diff --git a/src/common/fs/fs_util.cpp b/src/common/fs/fs_util.cpp
index 9f8671982..eb4ac1deb 100644
--- a/src/common/fs/fs_util.cpp
+++ b/src/common/fs/fs_util.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
@@ -16,6 +15,10 @@ std::u8string BufferToU8String(std::span<const u8> buffer) {
return std::u8string{buffer.begin(), std::ranges::find(buffer, u8{0})};
}
+std::u8string_view BufferToU8StringView(std::span<const u8> buffer) {
+ return std::u8string_view{reinterpret_cast<const char8_t*>(buffer.data())};
+}
+
std::string ToUTF8String(std::u8string_view u8_string) {
return std::string{u8_string.begin(), u8_string.end()};
}
@@ -24,6 +27,10 @@ std::string BufferToUTF8String(std::span<const u8> buffer) {
return std::string{buffer.begin(), std::ranges::find(buffer, u8{0})};
}
+std::string_view BufferToUTF8StringView(std::span<const u8> buffer) {
+ return std::string_view{reinterpret_cast<const char*>(buffer.data())};
+}
+
std::string PathToUTF8String(const std::filesystem::path& path) {
return ToUTF8String(path.u8string());
}
diff --git a/src/common/fs/fs_util.h b/src/common/fs/fs_util.h
index 1ec82eb35..2492a9f94 100644
--- a/src/common/fs/fs_util.h
+++ b/src/common/fs/fs_util.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,7 +7,6 @@
#include <filesystem>
#include <span>
#include <string>
-#include <string_view>
#include "common/common_types.h"
@@ -38,6 +36,15 @@ concept IsChar = std::same_as<T, char>;
[[nodiscard]] std::u8string BufferToU8String(std::span<const u8> buffer);
/**
+ * Same as BufferToU8String, but returns a string view of the buffer.
+ *
+ * @param buffer Buffer of bytes
+ *
+ * @returns UTF-8 encoded std::u8string_view.
+ */
+[[nodiscard]] std::u8string_view BufferToU8StringView(std::span<const u8> buffer);
+
+/**
* Converts a std::u8string or std::u8string_view to a UTF-8 encoded std::string.
*
* @param u8_string UTF-8 encoded u8string
@@ -58,6 +65,15 @@ concept IsChar = std::same_as<T, char>;
[[nodiscard]] std::string BufferToUTF8String(std::span<const u8> buffer);
/**
+ * Same as BufferToUTF8String, but returns a string view of the buffer.
+ *
+ * @param buffer Buffer of bytes
+ *
+ * @returns UTF-8 encoded std::string_view.
+ */
+[[nodiscard]] std::string_view BufferToUTF8StringView(std::span<const u8> buffer);
+
+/**
* Converts a filesystem path to a UTF-8 encoded std::string.
*
* @param path Filesystem path
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index 1bcb897b5..1074f2421 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <unordered_map>
@@ -233,9 +232,7 @@ void SetYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) {
fs::path GetExeDirectory() {
wchar_t exe_path[MAX_PATH];
- GetModuleFileNameW(nullptr, exe_path, MAX_PATH);
-
- if (!exe_path) {
+ if (GetModuleFileNameW(nullptr, exe_path, MAX_PATH) == 0) {
LOG_ERROR(Common_Filesystem,
"Failed to get the path to the executable of the current process");
}
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index 0a9e3a145..13d713f1e 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/hash.h b/src/common/hash.h
index 298930702..e8fe78b07 100644
--- a/src/common/hash.h
+++ b/src/common/hash.h
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -19,4 +18,11 @@ struct PairHash {
}
};
+template <typename T>
+struct IdentityHash {
+ [[nodiscard]] size_t operator()(T value) const noexcept {
+ return static_cast<size_t>(value);
+ }
+};
+
} // namespace Common
diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp
index 74f52dd11..07053295c 100644
--- a/src/common/hex_util.cpp
+++ b/src/common/hex_util.cpp
@@ -1,6 +1,6 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/hex_util.h"
diff --git a/src/common/hex_util.h b/src/common/hex_util.h
index 5e9b6ef8b..a00904939 100644
--- a/src/common/hex_util.h
+++ b/src/common/hex_util.h
@@ -1,13 +1,12 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
#include <string>
-#include <type_traits>
#include <vector>
#include <fmt/format.h>
#include "common/common_types.h"
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp
index 28949fe5e..7f9659612 100644
--- a/src/common/host_memory.cpp
+++ b/src/common/host_memory.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef _WIN32
@@ -18,6 +17,7 @@
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
+#include "common/scope_exit.h"
#endif // ^^^ Linux ^^^
@@ -27,7 +27,6 @@
#include "common/assert.h"
#include "common/host_memory.h"
#include "common/logging/log.h"
-#include "common/scope_exit.h"
namespace Common {
@@ -149,7 +148,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 +168,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);
@@ -327,8 +326,8 @@ private:
bool IsNiechePlaceholder(size_t virtual_offset, size_t length) const {
const auto it = placeholders.upper_bound({virtual_offset, virtual_offset + length});
if (it != placeholders.end() && it->lower() == virtual_offset + length) {
- const bool is_root = it == placeholders.begin() && virtual_offset == 0;
- return is_root || std::prev(it)->upper() == virtual_offset;
+ return it == placeholders.begin() ? virtual_offset == 0
+ : std::prev(it)->upper() == virtual_offset;
}
return false;
}
diff --git a/src/common/host_memory.h b/src/common/host_memory.h
index 9b8326d0f..447975ded 100644
--- a/src/common/host_memory.h
+++ b/src/common/host_memory.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/input.h b/src/common/input.h
index 54fcb24b0..bfa0639f5 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -28,7 +27,7 @@ enum class InputType {
Color,
Vibration,
Nfc,
- Ir,
+ IrSensor,
};
// Internal battery charge level
@@ -53,6 +52,15 @@ enum class PollingMode {
IR,
};
+enum class CameraFormat {
+ Size320x240,
+ Size160x120,
+ Size80x60,
+ Size40x30,
+ Size20x15,
+ None,
+};
+
// Vibration reply from the controller
enum class VibrationError {
None,
@@ -68,10 +76,31 @@ enum class PollingError {
Unknown,
};
+// Nfc reply from the controller
+enum class NfcState {
+ Success,
+ NewAmiibo,
+ WaitingForAmiibo,
+ AmiiboRemoved,
+ NotAnAmiibo,
+ NotSupported,
+ WrongDeviceState,
+ WriteFailed,
+ Unknown,
+};
+
+// Ir camera reply from the controller
+enum class CameraError {
+ None,
+ NotSupported,
+ Unknown,
+};
+
// Hint for amplification curve to be used
enum class VibrationAmplificationType {
Linear,
Exponential,
+ Test,
};
// Analog properties for calibration
@@ -86,6 +115,8 @@ struct AnalogProperties {
float offset{};
// Invert direction of the sensor data
bool inverted{};
+ // Press once to activate, press again to release
+ bool toggle{};
};
// Single analog sensor data
@@ -99,8 +130,11 @@ struct AnalogStatus {
struct ButtonStatus {
Common::UUID uuid{};
bool value{};
+ // Invert value of the button
bool inverted{};
+ // Press once to activate, press again to release
bool toggle{};
+ // Internal lock for the toggle status
bool locked{};
};
@@ -175,6 +209,17 @@ struct LedStatus {
bool led_4{};
};
+// Raw data fom camera
+struct CameraStatus {
+ CameraFormat format{CameraFormat::None};
+ std::vector<u8> data{};
+};
+
+struct NfcStatus {
+ NfcState state{};
+ std::vector<u8> data{};
+};
+
// List of buttons to be passed to Qt that can be translated
enum class ButtonNames {
Undefined,
@@ -232,6 +277,8 @@ struct CallbackStatus {
BodyColorStatus color_status{};
BatteryStatus battery_status{};
VibrationStatus vibration_status{};
+ CameraStatus camera_status{};
+ NfcStatus nfc_status{};
};
// Triggered once every input change
@@ -280,6 +327,18 @@ public:
virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
return PollingError::NotSupported;
}
+
+ virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
+ return CameraError::NotSupported;
+ }
+
+ virtual NfcState SupportsNfc() const {
+ return NfcState::NotSupported;
+ }
+
+ virtual NfcState WriteNfcData([[maybe_unused]] const std::vector<u8>& data) {
+ return NfcState::NotSupported;
+ }
};
/// An abstract class template for a factory that can create input devices.
diff --git a/src/common/intrusive_red_black_tree.h b/src/common/intrusive_red_black_tree.h
index 3173cc449..93046615e 100644
--- a/src/common/intrusive_red_black_tree.h
+++ b/src/common/intrusive_red_black_tree.h
@@ -1,9 +1,9 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include "common/common_funcs.h"
#include "common/parent_of_member.h"
#include "common/tree.h"
@@ -15,32 +15,33 @@ class IntrusiveRedBlackTreeImpl;
}
+#pragma pack(push, 4)
struct IntrusiveRedBlackTreeNode {
+ YUZU_NON_COPYABLE(IntrusiveRedBlackTreeNode);
+
public:
- using EntryType = RBEntry<IntrusiveRedBlackTreeNode>;
+ using RBEntry = freebsd::RBEntry<IntrusiveRedBlackTreeNode>;
- constexpr IntrusiveRedBlackTreeNode() = default;
+private:
+ RBEntry m_entry;
- void SetEntry(const EntryType& new_entry) {
- entry = new_entry;
- }
+public:
+ explicit IntrusiveRedBlackTreeNode() = default;
- [[nodiscard]] EntryType& GetEntry() {
- return entry;
+ [[nodiscard]] constexpr RBEntry& GetRBEntry() {
+ return m_entry;
}
-
- [[nodiscard]] const EntryType& GetEntry() const {
- return entry;
+ [[nodiscard]] constexpr const RBEntry& GetRBEntry() const {
+ return m_entry;
}
-private:
- EntryType entry{};
-
- friend class impl::IntrusiveRedBlackTreeImpl;
-
- template <class, class, class>
- friend class IntrusiveRedBlackTree;
+ constexpr void SetRBEntry(const RBEntry& entry) {
+ m_entry = entry;
+ }
};
+static_assert(sizeof(IntrusiveRedBlackTreeNode) ==
+ 3 * sizeof(void*) + std::max<size_t>(sizeof(freebsd::RBColor), 4));
+#pragma pack(pop)
template <class T, class Traits, class Comparator>
class IntrusiveRedBlackTree;
@@ -48,12 +49,17 @@ class IntrusiveRedBlackTree;
namespace impl {
class IntrusiveRedBlackTreeImpl {
+ YUZU_NON_COPYABLE(IntrusiveRedBlackTreeImpl);
+
private:
template <class, class, class>
friend class ::Common::IntrusiveRedBlackTree;
- using RootType = RBHead<IntrusiveRedBlackTreeNode>;
- RootType root;
+private:
+ using RootType = freebsd::RBHead<IntrusiveRedBlackTreeNode>;
+
+private:
+ RootType m_root;
public:
template <bool Const>
@@ -81,149 +87,150 @@ public:
IntrusiveRedBlackTreeImpl::reference>;
private:
- pointer node;
+ pointer m_node;
public:
- explicit Iterator(pointer n) : node(n) {}
+ constexpr explicit Iterator(pointer n) : m_node(n) {}
- bool operator==(const Iterator& rhs) const {
- return this->node == rhs.node;
+ constexpr bool operator==(const Iterator& rhs) const {
+ return m_node == rhs.m_node;
}
- bool operator!=(const Iterator& rhs) const {
+ constexpr bool operator!=(const Iterator& rhs) const {
return !(*this == rhs);
}
- pointer operator->() const {
- return this->node;
+ constexpr pointer operator->() const {
+ return m_node;
}
- reference operator*() const {
- return *this->node;
+ constexpr reference operator*() const {
+ return *m_node;
}
- Iterator& operator++() {
- this->node = GetNext(this->node);
+ constexpr Iterator& operator++() {
+ m_node = GetNext(m_node);
return *this;
}
- Iterator& operator--() {
- this->node = GetPrev(this->node);
+ constexpr Iterator& operator--() {
+ m_node = GetPrev(m_node);
return *this;
}
- Iterator operator++(int) {
+ constexpr Iterator operator++(int) {
const Iterator it{*this};
++(*this);
return it;
}
- Iterator operator--(int) {
+ constexpr Iterator operator--(int) {
const Iterator it{*this};
--(*this);
return it;
}
- operator Iterator<true>() const {
- return Iterator<true>(this->node);
+ constexpr operator Iterator<true>() const {
+ return Iterator<true>(m_node);
}
};
private:
- // Define accessors using RB_* functions.
- bool EmptyImpl() const {
- return root.IsEmpty();
+ constexpr bool EmptyImpl() const {
+ return m_root.IsEmpty();
}
- IntrusiveRedBlackTreeNode* GetMinImpl() const {
- return RB_MIN(const_cast<RootType*>(&root));
+ constexpr IntrusiveRedBlackTreeNode* GetMinImpl() const {
+ return freebsd::RB_MIN(const_cast<RootType&>(m_root));
}
- IntrusiveRedBlackTreeNode* GetMaxImpl() const {
- return RB_MAX(const_cast<RootType*>(&root));
+ constexpr IntrusiveRedBlackTreeNode* GetMaxImpl() const {
+ return freebsd::RB_MAX(const_cast<RootType&>(m_root));
}
- IntrusiveRedBlackTreeNode* RemoveImpl(IntrusiveRedBlackTreeNode* node) {
- return RB_REMOVE(&root, node);
+ constexpr IntrusiveRedBlackTreeNode* RemoveImpl(IntrusiveRedBlackTreeNode* node) {
+ return freebsd::RB_REMOVE(m_root, node);
}
public:
- static IntrusiveRedBlackTreeNode* GetNext(IntrusiveRedBlackTreeNode* node) {
- return RB_NEXT(node);
+ static constexpr IntrusiveRedBlackTreeNode* GetNext(IntrusiveRedBlackTreeNode* node) {
+ return freebsd::RB_NEXT(node);
}
- static IntrusiveRedBlackTreeNode* GetPrev(IntrusiveRedBlackTreeNode* node) {
- return RB_PREV(node);
+ static constexpr IntrusiveRedBlackTreeNode* GetPrev(IntrusiveRedBlackTreeNode* node) {
+ return freebsd::RB_PREV(node);
}
- static const IntrusiveRedBlackTreeNode* GetNext(const IntrusiveRedBlackTreeNode* node) {
+ static constexpr IntrusiveRedBlackTreeNode const* GetNext(
+ IntrusiveRedBlackTreeNode const* node) {
return static_cast<const IntrusiveRedBlackTreeNode*>(
GetNext(const_cast<IntrusiveRedBlackTreeNode*>(node)));
}
- static const IntrusiveRedBlackTreeNode* GetPrev(const IntrusiveRedBlackTreeNode* node) {
+ static constexpr IntrusiveRedBlackTreeNode const* GetPrev(
+ IntrusiveRedBlackTreeNode const* node) {
return static_cast<const IntrusiveRedBlackTreeNode*>(
GetPrev(const_cast<IntrusiveRedBlackTreeNode*>(node)));
}
public:
- constexpr IntrusiveRedBlackTreeImpl() {}
+ constexpr IntrusiveRedBlackTreeImpl() = default;
// Iterator accessors.
- iterator begin() {
+ constexpr iterator begin() {
return iterator(this->GetMinImpl());
}
- const_iterator begin() const {
+ constexpr const_iterator begin() const {
return const_iterator(this->GetMinImpl());
}
- iterator end() {
+ constexpr iterator end() {
return iterator(static_cast<IntrusiveRedBlackTreeNode*>(nullptr));
}
- const_iterator end() const {
+ constexpr const_iterator end() const {
return const_iterator(static_cast<const IntrusiveRedBlackTreeNode*>(nullptr));
}
- const_iterator cbegin() const {
+ constexpr const_iterator cbegin() const {
return this->begin();
}
- const_iterator cend() const {
+ constexpr const_iterator cend() const {
return this->end();
}
- iterator iterator_to(reference ref) {
- return iterator(&ref);
+ constexpr iterator iterator_to(reference ref) {
+ return iterator(std::addressof(ref));
}
- const_iterator iterator_to(const_reference ref) const {
- return const_iterator(&ref);
+ constexpr const_iterator iterator_to(const_reference ref) const {
+ return const_iterator(std::addressof(ref));
}
// Content management.
- bool empty() const {
+ constexpr bool empty() const {
return this->EmptyImpl();
}
- reference back() {
+ constexpr reference back() {
return *this->GetMaxImpl();
}
- const_reference back() const {
+ constexpr const_reference back() const {
return *this->GetMaxImpl();
}
- reference front() {
+ constexpr reference front() {
return *this->GetMinImpl();
}
- const_reference front() const {
+ constexpr const_reference front() const {
return *this->GetMinImpl();
}
- iterator erase(iterator it) {
+ constexpr iterator erase(iterator it) {
auto cur = std::addressof(*it);
auto next = GetNext(cur);
this->RemoveImpl(cur);
@@ -234,16 +241,16 @@ public:
} // namespace impl
template <typename T>
-concept HasLightCompareType = requires {
- { std::is_same<typename T::LightCompareType, void>::value } -> std::convertible_to<bool>;
+concept HasRedBlackKeyType = requires {
+ { std::is_same<typename T::RedBlackKeyType, void>::value } -> std::convertible_to<bool>;
};
namespace impl {
template <typename T, typename Default>
- consteval auto* GetLightCompareType() {
- if constexpr (HasLightCompareType<T>) {
- return static_cast<typename T::LightCompareType*>(nullptr);
+ consteval auto* GetRedBlackKeyType() {
+ if constexpr (HasRedBlackKeyType<T>) {
+ return static_cast<typename T::RedBlackKeyType*>(nullptr);
} else {
return static_cast<Default*>(nullptr);
}
@@ -252,16 +259,17 @@ namespace impl {
} // namespace impl
template <typename T, typename Default>
-using LightCompareType = std::remove_pointer_t<decltype(impl::GetLightCompareType<T, Default>())>;
+using RedBlackKeyType = std::remove_pointer_t<decltype(impl::GetRedBlackKeyType<T, Default>())>;
template <class T, class Traits, class Comparator>
class IntrusiveRedBlackTree {
+ YUZU_NON_COPYABLE(IntrusiveRedBlackTree);
public:
using ImplType = impl::IntrusiveRedBlackTreeImpl;
private:
- ImplType impl{};
+ ImplType m_impl;
public:
template <bool Const>
@@ -277,9 +285,9 @@ public:
using iterator = Iterator<false>;
using const_iterator = Iterator<true>;
- using light_value_type = LightCompareType<Comparator, value_type>;
- using const_light_pointer = const light_value_type*;
- using const_light_reference = const light_value_type&;
+ using key_type = RedBlackKeyType<Comparator, value_type>;
+ using const_key_pointer = const key_type*;
+ using const_key_reference = const key_type&;
template <bool Const>
class Iterator {
@@ -298,183 +306,201 @@ public:
IntrusiveRedBlackTree::reference>;
private:
- ImplIterator iterator;
+ ImplIterator m_impl;
private:
- explicit Iterator(ImplIterator it) : iterator(it) {}
+ constexpr explicit Iterator(ImplIterator it) : m_impl(it) {}
- explicit Iterator(typename std::conditional<Const, ImplType::const_iterator,
- ImplType::iterator>::type::pointer ptr)
- : iterator(ptr) {}
+ constexpr explicit Iterator(typename ImplIterator::pointer p) : m_impl(p) {}
- ImplIterator GetImplIterator() const {
- return this->iterator;
+ constexpr ImplIterator GetImplIterator() const {
+ return m_impl;
}
public:
- bool operator==(const Iterator& rhs) const {
- return this->iterator == rhs.iterator;
+ constexpr bool operator==(const Iterator& rhs) const {
+ return m_impl == rhs.m_impl;
}
- bool operator!=(const Iterator& rhs) const {
+ constexpr bool operator!=(const Iterator& rhs) const {
return !(*this == rhs);
}
- pointer operator->() const {
- return Traits::GetParent(std::addressof(*this->iterator));
+ constexpr pointer operator->() const {
+ return Traits::GetParent(std::addressof(*m_impl));
}
- reference operator*() const {
- return *Traits::GetParent(std::addressof(*this->iterator));
+ constexpr reference operator*() const {
+ return *Traits::GetParent(std::addressof(*m_impl));
}
- Iterator& operator++() {
- ++this->iterator;
+ constexpr Iterator& operator++() {
+ ++m_impl;
return *this;
}
- Iterator& operator--() {
- --this->iterator;
+ constexpr Iterator& operator--() {
+ --m_impl;
return *this;
}
- Iterator operator++(int) {
+ constexpr Iterator operator++(int) {
const Iterator it{*this};
- ++this->iterator;
+ ++m_impl;
return it;
}
- Iterator operator--(int) {
+ constexpr Iterator operator--(int) {
const Iterator it{*this};
- --this->iterator;
+ --m_impl;
return it;
}
- operator Iterator<true>() const {
- return Iterator<true>(this->iterator);
+ constexpr operator Iterator<true>() const {
+ return Iterator<true>(m_impl);
}
};
private:
- static int CompareImpl(const IntrusiveRedBlackTreeNode* lhs,
- const IntrusiveRedBlackTreeNode* rhs) {
+ static constexpr int CompareImpl(const IntrusiveRedBlackTreeNode* lhs,
+ const IntrusiveRedBlackTreeNode* rhs) {
return Comparator::Compare(*Traits::GetParent(lhs), *Traits::GetParent(rhs));
}
- static int LightCompareImpl(const void* elm, const IntrusiveRedBlackTreeNode* rhs) {
- return Comparator::Compare(*static_cast<const_light_pointer>(elm), *Traits::GetParent(rhs));
+ static constexpr int CompareKeyImpl(const_key_reference key,
+ const IntrusiveRedBlackTreeNode* rhs) {
+ return Comparator::Compare(key, *Traits::GetParent(rhs));
}
// Define accessors using RB_* functions.
- IntrusiveRedBlackTreeNode* InsertImpl(IntrusiveRedBlackTreeNode* node) {
- return RB_INSERT(&impl.root, node, CompareImpl);
+ constexpr IntrusiveRedBlackTreeNode* InsertImpl(IntrusiveRedBlackTreeNode* node) {
+ return freebsd::RB_INSERT(m_impl.m_root, node, CompareImpl);
}
- IntrusiveRedBlackTreeNode* FindImpl(const IntrusiveRedBlackTreeNode* node) const {
- return RB_FIND(const_cast<ImplType::RootType*>(&impl.root),
- const_cast<IntrusiveRedBlackTreeNode*>(node), CompareImpl);
+ constexpr IntrusiveRedBlackTreeNode* FindImpl(IntrusiveRedBlackTreeNode const* node) const {
+ return freebsd::RB_FIND(const_cast<ImplType::RootType&>(m_impl.m_root),
+ const_cast<IntrusiveRedBlackTreeNode*>(node), CompareImpl);
}
- IntrusiveRedBlackTreeNode* NFindImpl(const IntrusiveRedBlackTreeNode* node) const {
- return RB_NFIND(const_cast<ImplType::RootType*>(&impl.root),
- const_cast<IntrusiveRedBlackTreeNode*>(node), CompareImpl);
+ constexpr IntrusiveRedBlackTreeNode* NFindImpl(IntrusiveRedBlackTreeNode const* node) const {
+ return freebsd::RB_NFIND(const_cast<ImplType::RootType&>(m_impl.m_root),
+ const_cast<IntrusiveRedBlackTreeNode*>(node), CompareImpl);
}
- IntrusiveRedBlackTreeNode* FindLightImpl(const_light_pointer lelm) const {
- return RB_FIND_LIGHT(const_cast<ImplType::RootType*>(&impl.root),
- static_cast<const void*>(lelm), LightCompareImpl);
+ constexpr IntrusiveRedBlackTreeNode* FindKeyImpl(const_key_reference key) const {
+ return freebsd::RB_FIND_KEY(const_cast<ImplType::RootType&>(m_impl.m_root), key,
+ CompareKeyImpl);
}
- IntrusiveRedBlackTreeNode* NFindLightImpl(const_light_pointer lelm) const {
- return RB_NFIND_LIGHT(const_cast<ImplType::RootType*>(&impl.root),
- static_cast<const void*>(lelm), LightCompareImpl);
+ constexpr IntrusiveRedBlackTreeNode* NFindKeyImpl(const_key_reference key) const {
+ return freebsd::RB_NFIND_KEY(const_cast<ImplType::RootType&>(m_impl.m_root), key,
+ CompareKeyImpl);
+ }
+
+ constexpr IntrusiveRedBlackTreeNode* FindExistingImpl(
+ IntrusiveRedBlackTreeNode const* node) const {
+ return freebsd::RB_FIND_EXISTING(const_cast<ImplType::RootType&>(m_impl.m_root),
+ const_cast<IntrusiveRedBlackTreeNode*>(node), CompareImpl);
+ }
+
+ constexpr IntrusiveRedBlackTreeNode* FindExistingKeyImpl(const_key_reference key) const {
+ return freebsd::RB_FIND_EXISTING_KEY(const_cast<ImplType::RootType&>(m_impl.m_root), key,
+ CompareKeyImpl);
}
public:
constexpr IntrusiveRedBlackTree() = default;
// Iterator accessors.
- iterator begin() {
- return iterator(this->impl.begin());
+ constexpr iterator begin() {
+ return iterator(m_impl.begin());
}
- const_iterator begin() const {
- return const_iterator(this->impl.begin());
+ constexpr const_iterator begin() const {
+ return const_iterator(m_impl.begin());
}
- iterator end() {
- return iterator(this->impl.end());
+ constexpr iterator end() {
+ return iterator(m_impl.end());
}
- const_iterator end() const {
- return const_iterator(this->impl.end());
+ constexpr const_iterator end() const {
+ return const_iterator(m_impl.end());
}
- const_iterator cbegin() const {
+ constexpr const_iterator cbegin() const {
return this->begin();
}
- const_iterator cend() const {
+ constexpr const_iterator cend() const {
return this->end();
}
- iterator iterator_to(reference ref) {
- return iterator(this->impl.iterator_to(*Traits::GetNode(std::addressof(ref))));
+ constexpr iterator iterator_to(reference ref) {
+ return iterator(m_impl.iterator_to(*Traits::GetNode(std::addressof(ref))));
}
- const_iterator iterator_to(const_reference ref) const {
- return const_iterator(this->impl.iterator_to(*Traits::GetNode(std::addressof(ref))));
+ constexpr const_iterator iterator_to(const_reference ref) const {
+ return const_iterator(m_impl.iterator_to(*Traits::GetNode(std::addressof(ref))));
}
// Content management.
- bool empty() const {
- return this->impl.empty();
+ constexpr bool empty() const {
+ return m_impl.empty();
}
- reference back() {
- return *Traits::GetParent(std::addressof(this->impl.back()));
+ constexpr reference back() {
+ return *Traits::GetParent(std::addressof(m_impl.back()));
}
- const_reference back() const {
- return *Traits::GetParent(std::addressof(this->impl.back()));
+ constexpr const_reference back() const {
+ return *Traits::GetParent(std::addressof(m_impl.back()));
}
- reference front() {
- return *Traits::GetParent(std::addressof(this->impl.front()));
+ constexpr reference front() {
+ return *Traits::GetParent(std::addressof(m_impl.front()));
}
- const_reference front() const {
- return *Traits::GetParent(std::addressof(this->impl.front()));
+ constexpr const_reference front() const {
+ return *Traits::GetParent(std::addressof(m_impl.front()));
}
- iterator erase(iterator it) {
- return iterator(this->impl.erase(it.GetImplIterator()));
+ constexpr iterator erase(iterator it) {
+ return iterator(m_impl.erase(it.GetImplIterator()));
}
- iterator insert(reference ref) {
+ constexpr iterator insert(reference ref) {
ImplType::pointer node = Traits::GetNode(std::addressof(ref));
this->InsertImpl(node);
return iterator(node);
}
- iterator find(const_reference ref) const {
+ constexpr iterator find(const_reference ref) const {
return iterator(this->FindImpl(Traits::GetNode(std::addressof(ref))));
}
- iterator nfind(const_reference ref) const {
+ constexpr iterator nfind(const_reference ref) const {
return iterator(this->NFindImpl(Traits::GetNode(std::addressof(ref))));
}
- iterator find_light(const_light_reference ref) const {
- return iterator(this->FindLightImpl(std::addressof(ref)));
+ constexpr iterator find_key(const_key_reference ref) const {
+ return iterator(this->FindKeyImpl(ref));
+ }
+
+ constexpr iterator nfind_key(const_key_reference ref) const {
+ return iterator(this->NFindKeyImpl(ref));
+ }
+
+ constexpr iterator find_existing(const_reference ref) const {
+ return iterator(this->FindExistingImpl(Traits::GetNode(std::addressof(ref))));
}
- iterator nfind_light(const_light_reference ref) const {
- return iterator(this->NFindLightImpl(std::addressof(ref)));
+ constexpr iterator find_existing_key(const_key_reference ref) const {
+ return iterator(this->FindExistingKeyImpl(ref));
}
};
-template <auto T, class Derived = impl::GetParentType<T>>
+template <auto T, class Derived = Common::impl::GetParentType<T>>
class IntrusiveRedBlackTreeMemberTraits;
template <class Parent, IntrusiveRedBlackTreeNode Parent::*Member, class Derived>
@@ -498,19 +524,16 @@ private:
return std::addressof(parent->*Member);
}
- static constexpr Derived* GetParent(IntrusiveRedBlackTreeNode* node) {
- return GetParentPointer<Member, Derived>(node);
+ static Derived* GetParent(IntrusiveRedBlackTreeNode* node) {
+ return Common::GetParentPointer<Member, Derived>(node);
}
- static constexpr Derived const* GetParent(const IntrusiveRedBlackTreeNode* node) {
- return GetParentPointer<Member, Derived>(node);
+ static Derived const* GetParent(IntrusiveRedBlackTreeNode const* node) {
+ return Common::GetParentPointer<Member, Derived>(node);
}
-
-private:
- static constexpr TypedStorage<Derived> DerivedStorage = {};
};
-template <auto T, class Derived = impl::GetParentType<T>>
+template <auto T, class Derived = Common::impl::GetParentType<T>>
class IntrusiveRedBlackTreeMemberTraitsDeferredAssert;
template <class Parent, IntrusiveRedBlackTreeNode Parent::*Member, class Derived>
@@ -521,11 +544,6 @@ public:
IntrusiveRedBlackTree<Derived, IntrusiveRedBlackTreeMemberTraitsDeferredAssert, Comparator>;
using TreeTypeImpl = impl::IntrusiveRedBlackTreeImpl;
- static constexpr bool IsValid() {
- TypedStorage<Derived> DerivedStorage = {};
- return GetParent(GetNode(GetPointer(DerivedStorage))) == GetPointer(DerivedStorage);
- }
-
private:
template <class, class, class>
friend class IntrusiveRedBlackTree;
@@ -540,30 +558,36 @@ private:
return std::addressof(parent->*Member);
}
- static constexpr Derived* GetParent(IntrusiveRedBlackTreeNode* node) {
- return GetParentPointer<Member, Derived>(node);
+ static Derived* GetParent(IntrusiveRedBlackTreeNode* node) {
+ return Common::GetParentPointer<Member, Derived>(node);
}
- static constexpr Derived const* GetParent(const IntrusiveRedBlackTreeNode* node) {
- return GetParentPointer<Member, Derived>(node);
+ static Derived const* GetParent(IntrusiveRedBlackTreeNode const* node) {
+ return Common::GetParentPointer<Member, Derived>(node);
}
};
template <class Derived>
-class IntrusiveRedBlackTreeBaseNode : public IntrusiveRedBlackTreeNode {
+class alignas(void*) IntrusiveRedBlackTreeBaseNode : public IntrusiveRedBlackTreeNode {
public:
+ using IntrusiveRedBlackTreeNode::IntrusiveRedBlackTreeNode;
+
constexpr Derived* GetPrev() {
- return static_cast<Derived*>(impl::IntrusiveRedBlackTreeImpl::GetPrev(this));
+ return static_cast<Derived*>(static_cast<IntrusiveRedBlackTreeBaseNode*>(
+ impl::IntrusiveRedBlackTreeImpl::GetPrev(this)));
}
constexpr const Derived* GetPrev() const {
- return static_cast<const Derived*>(impl::IntrusiveRedBlackTreeImpl::GetPrev(this));
+ return static_cast<const Derived*>(static_cast<const IntrusiveRedBlackTreeBaseNode*>(
+ impl::IntrusiveRedBlackTreeImpl::GetPrev(this)));
}
constexpr Derived* GetNext() {
- return static_cast<Derived*>(impl::IntrusiveRedBlackTreeImpl::GetNext(this));
+ return static_cast<Derived*>(static_cast<IntrusiveRedBlackTreeBaseNode*>(
+ impl::IntrusiveRedBlackTreeImpl::GetNext(this)));
}
constexpr const Derived* GetNext() const {
- return static_cast<const Derived*>(impl::IntrusiveRedBlackTreeImpl::GetNext(this));
+ return static_cast<const Derived*>(static_cast<const IntrusiveRedBlackTreeBaseNode*>(
+ impl::IntrusiveRedBlackTreeImpl::GetNext(this)));
}
};
@@ -581,19 +605,22 @@ private:
friend class impl::IntrusiveRedBlackTreeImpl;
static constexpr IntrusiveRedBlackTreeNode* GetNode(Derived* parent) {
- return static_cast<IntrusiveRedBlackTreeNode*>(parent);
+ return static_cast<IntrusiveRedBlackTreeNode*>(
+ static_cast<IntrusiveRedBlackTreeBaseNode<Derived>*>(parent));
}
static constexpr IntrusiveRedBlackTreeNode const* GetNode(Derived const* parent) {
- return static_cast<const IntrusiveRedBlackTreeNode*>(parent);
+ return static_cast<const IntrusiveRedBlackTreeNode*>(
+ static_cast<const IntrusiveRedBlackTreeBaseNode<Derived>*>(parent));
}
static constexpr Derived* GetParent(IntrusiveRedBlackTreeNode* node) {
- return static_cast<Derived*>(node);
+ return static_cast<Derived*>(static_cast<IntrusiveRedBlackTreeBaseNode<Derived>*>(node));
}
- static constexpr Derived const* GetParent(const IntrusiveRedBlackTreeNode* node) {
- return static_cast<const Derived*>(node);
+ static constexpr Derived const* GetParent(IntrusiveRedBlackTreeNode const* node) {
+ return static_cast<const Derived*>(
+ static_cast<const IntrusiveRedBlackTreeBaseNode<Derived>*>(node));
}
};
diff --git a/src/common/literals.h b/src/common/literals.h
index d55fed40b..0ad314afb 100644
--- a/src/common/literals.h
+++ b/src/common/literals.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index c51c05b28..15d92505e 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -1,14 +1,11 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
#include <chrono>
#include <climits>
-#include <exception>
#include <stop_token>
#include <thread>
-#include <vector>
#include <fmt/format.h>
@@ -218,19 +215,17 @@ private:
Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_)
: filter{filter_}, file_backend{file_backend_filename} {}
- ~Impl() {
- StopBackendThread();
- }
+ ~Impl() = default;
void StartBackendThread() {
- backend_thread = std::thread([this] {
- Common::SetCurrentThreadName("yuzu:Log");
+ backend_thread = std::jthread([this](std::stop_token stop_token) {
+ Common::SetCurrentThreadName("Logger");
Entry entry;
const auto write_logs = [this, &entry]() {
ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
};
- while (!stop.stop_requested()) {
- entry = message_queue.PopWait(stop.get_token());
+ while (!stop_token.stop_requested()) {
+ entry = message_queue.PopWait(stop_token);
if (entry.filename != nullptr) {
write_logs();
}
@@ -244,11 +239,6 @@ private:
});
}
- void StopBackendThread() {
- stop.request_stop();
- backend_thread.join();
- }
-
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
const char* function, std::string&& message) const {
using std::chrono::duration_cast;
@@ -283,10 +273,9 @@ private:
ColorConsoleBackend color_console_backend{};
FileBackend file_backend;
- std::stop_source stop;
- std::thread backend_thread;
MPSCQueue<Entry, true> message_queue{};
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
+ std::jthread backend_thread;
};
} // namespace
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index bf785f402..12e5e2498 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -1,10 +1,8 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <filesystem>
#include "common/logging/filter.h"
namespace Common::Log {
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
index b898a652c..a959acb74 100644
--- a/src/common/logging/filter.cpp
+++ b/src/common/logging/filter.cpp
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "common/logging/filter.h"
@@ -101,6 +100,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) \
@@ -108,6 +108,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Service, Migration) \
SUB(Service, Mii) \
SUB(Service, MM) \
+ SUB(Service, MNPP) \
SUB(Service, NCM) \
SUB(Service, NFC) \
SUB(Service, NFP) \
@@ -118,6 +119,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) \
@@ -125,7 +127,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Service, PM) \
SUB(Service, PREPO) \
SUB(Service, PSC) \
- SUB(Service, PSM) \
+ SUB(Service, PTM) \
SUB(Service, SET) \
SUB(Service, SM) \
SUB(Service, SPL) \
diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h
index 1a3074e04..54d172cc0 100644
--- a/src/common/logging/filter.h
+++ b/src/common/logging/filter.h
@@ -1,13 +1,11 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <chrono>
#include <cstddef>
-#include <string_view>
#include "common/logging/log.h"
namespace Common::Log {
diff --git a/src/common/logging/formatter.h b/src/common/logging/formatter.h
index 552cde75a..88e55505d 100644
--- a/src/common/logging/formatter.h
+++ b/src/common/logging/formatter.h
@@ -1,6 +1,5 @@
-// Copyright 2022 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 0c80d01ee..c00c01a9e 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/logging/log_entry.h b/src/common/logging/log_entry.h
index b28570071..d8d7daf76 100644
--- a/src/common/logging/log_entry.h
+++ b/src/common/logging/log_entry.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp
index 10b2281db..09398ea64 100644
--- a/src/common/logging/text_formatter.cpp
+++ b/src/common/logging/text_formatter.cpp
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <cstdio>
@@ -10,12 +9,10 @@
#endif
#include "common/assert.h"
-#include "common/common_funcs.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "common/logging/log_entry.h"
#include "common/logging/text_formatter.h"
-#include "common/string_util.h"
namespace Common::Log {
diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h
index 171e74cfe..0d0ec4370 100644
--- a/src/common/logging/text_formatter.h
+++ b/src/common/logging/text_formatter.h
@@ -1,10 +1,8 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <cstddef>
#include <string>
namespace Common::Log {
diff --git a/src/common/logging/types.h b/src/common/logging/types.h
index 9ed0c7ad6..595c15ada 100644
--- a/src/common/logging/types.h
+++ b/src/common/logging/types.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -69,6 +68,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
@@ -76,6 +76,7 @@ enum class Class : u8 {
Service_Migration, ///< The migration service
Service_Mii, ///< The Mii service
Service_MM, ///< The MM (Multimedia) service
+ Service_MNPP, ///< The MNPP service
Service_NCM, ///< The NCM service
Service_NFC, ///< The NFC (Near-field communication) service
Service_NFP, ///< The NFP service
@@ -86,6 +87,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
@@ -93,7 +95,7 @@ enum class Class : u8 {
Service_PM, ///< The PM service
Service_PREPO, ///< The PREPO (Play report) service
Service_PSC, ///< The PSC service
- Service_PSM, ///< The PSM service
+ Service_PTM, ///< The PTM service
Service_SET, ///< The SET (Settings) service
Service_SM, ///< The SM (Service manager) service
Service_SPL, ///< The SPL service
diff --git a/src/common/lru_cache.h b/src/common/lru_cache.h
index 365488ba5..36cea5d27 100644
--- a/src/common/lru_cache.h
+++ b/src/common/lru_cache.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2+ or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/lz4_compression.cpp b/src/common/lz4_compression.cpp
index dbb40da7c..ffb32fecf 100644
--- a/src/common/lz4_compression.cpp
+++ b/src/common/lz4_compression.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <lz4hc.h>
diff --git a/src/common/lz4_compression.h b/src/common/lz4_compression.h
index 1b4717595..7fd53a960 100644
--- a/src/common/lz4_compression.h
+++ b/src/common/lz4_compression.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/math_util.h b/src/common/math_util.h
index 510c4e56d..1f5928c15 100644
--- a/src/common/math_util.h
+++ b/src/common/math_util.h
@@ -1,9 +1,10 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#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/memory_detect.cpp b/src/common/memory_detect.cpp
index 8cff6ec37..86a3abcc6 100644
--- a/src/common/memory_detect.cpp
+++ b/src/common/memory_detect.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef _WIN32
// clang-format off
@@ -70,4 +69,4 @@ const MemoryInfo& GetMemInfo() {
return mem_info;
}
-} // namespace Common \ No newline at end of file
+} // namespace Common
diff --git a/src/common/memory_detect.h b/src/common/memory_detect.h
index 0f73751c8..a345e6d28 100644
--- a/src/common/memory_detect.h
+++ b/src/common/memory_detect.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/microprofile.cpp b/src/common/microprofile.cpp
index ee25dd37f..e6657c82f 100644
--- a/src/common/microprofile.cpp
+++ b/src/common/microprofile.cpp
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
// Includes the MicroProfile implementation in this file for compilation
#define MICROPROFILE_IMPL 1
diff --git a/src/common/microprofile.h b/src/common/microprofile.h
index 54e7f3cc4..56ef0a2dc 100644
--- a/src/common/microprofile.h
+++ b/src/common/microprofile.h
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -23,12 +22,3 @@ typedef void* HANDLE;
#include <microprofile.h>
#define MP_RGB(r, g, b) ((r) << 16 | (g) << 8 | (b) << 0)
-
-// On OS X, some Mach header included by MicroProfile defines these as macros, conflicting with
-// identifiers we use.
-#ifdef PAGE_SIZE
-#undef PAGE_SIZE
-#endif
-#ifdef PAGE_MASK
-#undef PAGE_MASK
-#endif
diff --git a/src/common/microprofileui.h b/src/common/microprofileui.h
index 41abe6b75..39ed18ffa 100644
--- a/src/common/microprofileui.h
+++ b/src/common/microprofileui.h
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/multi_level_page_table.cpp b/src/common/multi_level_page_table.cpp
new file mode 100644
index 000000000..46e362f3b
--- /dev/null
+++ b/src/common/multi_level_page_table.cpp
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/multi_level_page_table.inc"
+
+namespace Common {
+template class Common::MultiLevelPageTable<u64>;
+template class Common::MultiLevelPageTable<u32>;
+} // namespace Common
diff --git a/src/common/multi_level_page_table.h b/src/common/multi_level_page_table.h
new file mode 100644
index 000000000..31f6676a0
--- /dev/null
+++ b/src/common/multi_level_page_table.h
@@ -0,0 +1,78 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Common {
+
+template <typename BaseAddr>
+class MultiLevelPageTable final {
+public:
+ constexpr MultiLevelPageTable() = default;
+ explicit MultiLevelPageTable(std::size_t address_space_bits, std::size_t first_level_bits,
+ std::size_t page_bits);
+
+ ~MultiLevelPageTable() noexcept;
+
+ MultiLevelPageTable(const MultiLevelPageTable&) = delete;
+ MultiLevelPageTable& operator=(const MultiLevelPageTable&) = delete;
+
+ MultiLevelPageTable(MultiLevelPageTable&& other) noexcept
+ : address_space_bits{std::exchange(other.address_space_bits, 0)},
+ first_level_bits{std::exchange(other.first_level_bits, 0)}, page_bits{std::exchange(
+ other.page_bits, 0)},
+ first_level_shift{std::exchange(other.first_level_shift, 0)},
+ first_level_chunk_size{std::exchange(other.first_level_chunk_size, 0)},
+ first_level_map{std::move(other.first_level_map)}, base_ptr{std::exchange(other.base_ptr,
+ nullptr)} {}
+
+ MultiLevelPageTable& operator=(MultiLevelPageTable&& other) noexcept {
+ address_space_bits = std::exchange(other.address_space_bits, 0);
+ first_level_bits = std::exchange(other.first_level_bits, 0);
+ page_bits = std::exchange(other.page_bits, 0);
+ first_level_shift = std::exchange(other.first_level_shift, 0);
+ first_level_chunk_size = std::exchange(other.first_level_chunk_size, 0);
+ alloc_size = std::exchange(other.alloc_size, 0);
+ first_level_map = std::move(other.first_level_map);
+ base_ptr = std::exchange(other.base_ptr, nullptr);
+ return *this;
+ }
+
+ void ReserveRange(u64 start, std::size_t size);
+
+ [[nodiscard]] const BaseAddr& operator[](std::size_t index) const {
+ return base_ptr[index];
+ }
+
+ [[nodiscard]] BaseAddr& operator[](std::size_t index) {
+ return base_ptr[index];
+ }
+
+ [[nodiscard]] BaseAddr* data() {
+ return base_ptr;
+ }
+
+ [[nodiscard]] const BaseAddr* data() const {
+ return base_ptr;
+ }
+
+private:
+ void AllocateLevel(u64 level);
+
+ std::size_t address_space_bits{};
+ std::size_t first_level_bits{};
+ std::size_t page_bits{};
+ std::size_t first_level_shift{};
+ std::size_t first_level_chunk_size{};
+ std::size_t alloc_size{};
+ std::vector<void*> first_level_map{};
+ BaseAddr* base_ptr{};
+};
+
+} // namespace Common
diff --git a/src/common/multi_level_page_table.inc b/src/common/multi_level_page_table.inc
new file mode 100644
index 000000000..8ac506fa0
--- /dev/null
+++ b/src/common/multi_level_page_table.inc
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <sys/mman.h>
+#endif
+
+#include "common/assert.h"
+#include "common/multi_level_page_table.h"
+
+namespace Common {
+
+template <typename BaseAddr>
+MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_,
+ std::size_t first_level_bits_,
+ std::size_t page_bits_)
+ : address_space_bits{address_space_bits_},
+ first_level_bits{first_level_bits_}, page_bits{page_bits_} {
+ if (page_bits == 0) {
+ return;
+ }
+ first_level_shift = address_space_bits - first_level_bits;
+ first_level_chunk_size = (1ULL << (first_level_shift - page_bits)) * sizeof(BaseAddr);
+ alloc_size = (1ULL << (address_space_bits - page_bits)) * sizeof(BaseAddr);
+ std::size_t first_level_size = 1ULL << first_level_bits;
+ first_level_map.resize(first_level_size, nullptr);
+#ifdef _WIN32
+ void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)};
+#else
+ void* base{mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)};
+
+ if (base == MAP_FAILED) {
+ base = nullptr;
+ }
+#endif
+
+ ASSERT(base);
+ base_ptr = reinterpret_cast<BaseAddr*>(base);
+}
+
+template <typename BaseAddr>
+MultiLevelPageTable<BaseAddr>::~MultiLevelPageTable() noexcept {
+ if (!base_ptr) {
+ return;
+ }
+#ifdef _WIN32
+ ASSERT(VirtualFree(base_ptr, 0, MEM_RELEASE));
+#else
+ ASSERT(munmap(base_ptr, alloc_size) == 0);
+#endif
+}
+
+template <typename BaseAddr>
+void MultiLevelPageTable<BaseAddr>::ReserveRange(u64 start, std::size_t size) {
+ const u64 new_start = start >> first_level_shift;
+ const u64 new_end = (start + size) >> first_level_shift;
+ for (u64 i = new_start; i <= new_end; i++) {
+ if (!first_level_map[i]) {
+ AllocateLevel(i);
+ }
+ }
+}
+
+template <typename BaseAddr>
+void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 level) {
+ void* ptr = reinterpret_cast<char *>(base_ptr) + level * first_level_chunk_size;
+#ifdef _WIN32
+ void* base{VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE)};
+#else
+ void* base{mmap(ptr, first_level_chunk_size, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)};
+
+ if (base == MAP_FAILED) {
+ base = nullptr;
+ }
+#endif
+ ASSERT(base);
+
+ first_level_map[level] = base;
+}
+
+} // namespace Common
diff --git a/src/common/nvidia_flags.cpp b/src/common/nvidia_flags.cpp
index d1afd1f1d..7ed7690ee 100644
--- a/src/common/nvidia_flags.cpp
+++ b/src/common/nvidia_flags.cpp
@@ -1,12 +1,10 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdlib>
#include <fmt/format.h>
-#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/nvidia_flags.h"
diff --git a/src/common/nvidia_flags.h b/src/common/nvidia_flags.h
index 8930efcec..8c3b1bfb9 100644
--- a/src/common/nvidia_flags.h
+++ b/src/common/nvidia_flags.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp
index 9fffd816f..b744b68ce 100644
--- a/src/common/page_table.cpp
+++ b/src/common/page_table.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/page_table.h"
@@ -10,11 +9,65 @@ PageTable::PageTable() = default;
PageTable::~PageTable() noexcept = default;
-void PageTable::Resize(size_t address_space_width_in_bits, size_t page_size_in_bits) {
- const size_t num_page_table_entries{1ULL << (address_space_width_in_bits - page_size_in_bits)};
+bool PageTable::BeginTraversal(TraversalEntry& out_entry, TraversalContext& out_context,
+ u64 address) const {
+ // Setup invalid defaults.
+ out_entry.phys_addr = 0;
+ out_entry.block_size = page_size;
+ out_context.next_page = 0;
+
+ // Validate that we can read the actual entry.
+ const auto page = address / page_size;
+ if (page >= backing_addr.size()) {
+ return false;
+ }
+
+ // Validate that the entry is mapped.
+ const auto phys_addr = backing_addr[page];
+ if (phys_addr == 0) {
+ return false;
+ }
+
+ // Populate the results.
+ out_entry.phys_addr = phys_addr + address;
+ out_context.next_page = page + 1;
+ out_context.next_offset = address + page_size;
+
+ return true;
+}
+
+bool PageTable::ContinueTraversal(TraversalEntry& out_entry, TraversalContext& context) const {
+ // Setup invalid defaults.
+ out_entry.phys_addr = 0;
+ out_entry.block_size = page_size;
+
+ // Validate that we can read the actual entry.
+ const auto page = context.next_page;
+ if (page >= backing_addr.size()) {
+ return false;
+ }
+
+ // Validate that the entry is mapped.
+ const auto phys_addr = backing_addr[page];
+ if (phys_addr == 0) {
+ return false;
+ }
+
+ // Populate the results.
+ out_entry.phys_addr = phys_addr + context.next_offset;
+ context.next_page = page + 1;
+ context.next_offset += page_size;
+
+ return true;
+}
+
+void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits) {
+ const std::size_t num_page_table_entries{1ULL
+ << (address_space_width_in_bits - page_size_in_bits)};
pointers.resize(num_page_table_entries);
backing_addr.resize(num_page_table_entries);
current_address_space_width_in_bits = address_space_width_in_bits;
+ page_size = 1ULL << page_size_in_bits;
}
} // namespace Common
diff --git a/src/common/page_table.h b/src/common/page_table.h
index 8267e8b4d..1ad3a9f8b 100644
--- a/src/common/page_table.h
+++ b/src/common/page_table.h
@@ -1,11 +1,9 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
-#include <tuple>
#include "common/common_types.h"
#include "common/virtual_buffer.h"
@@ -17,6 +15,9 @@ enum class PageType : u8 {
Unmapped,
/// Page is mapped to regular memory. This is the only type you can get pointers to.
Memory,
+ /// Page is mapped to regular memory, but inaccessible from CPU fastmem and must use
+ /// the callbacks.
+ DebugMemory,
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
/// invalidation
RasterizerCachedMemory,
@@ -27,6 +28,16 @@ enum class PageType : u8 {
* mimics the way a real CPU page table works.
*/
struct PageTable {
+ struct TraversalEntry {
+ u64 phys_addr{};
+ std::size_t block_size{};
+ };
+
+ struct TraversalContext {
+ u64 next_page{};
+ u64 next_offset{};
+ };
+
/// Number of bits reserved for attribute tagging.
/// This can be at most the guaranteed alignment of the pointers in the page table.
static constexpr int ATTRIBUTE_BITS = 2;
@@ -89,6 +100,10 @@ struct PageTable {
PageTable(PageTable&&) noexcept = default;
PageTable& operator=(PageTable&&) noexcept = default;
+ bool BeginTraversal(TraversalEntry& out_entry, TraversalContext& out_context,
+ u64 address) const;
+ bool ContinueTraversal(TraversalEntry& out_entry, TraversalContext& context) const;
+
/**
* Resizes the page table to be able to accommodate enough pages within
* a given address space.
@@ -96,9 +111,9 @@ struct PageTable {
* @param address_space_width_in_bits The address size width in bits.
* @param page_size_in_bits The page size in bits.
*/
- void Resize(size_t address_space_width_in_bits, size_t page_size_in_bits);
+ void Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits);
- size_t GetAddressSpaceBits() const {
+ std::size_t GetAddressSpaceBits() const {
return current_address_space_width_in_bits;
}
@@ -110,9 +125,11 @@ struct PageTable {
VirtualBuffer<u64> backing_addr;
- size_t current_address_space_width_in_bits;
+ std::size_t current_address_space_width_in_bits{};
+
+ u8* fastmem_arena{};
- u8* fastmem_arena;
+ std::size_t page_size{};
};
} // namespace Common
diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp
index bbf20f5eb..629babb81 100644
--- a/src/common/param_package.cpp
+++ b/src/common/param_package.cpp
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <stdexcept>
@@ -76,7 +75,7 @@ std::string ParamPackage::Serialize() const {
std::string ParamPackage::Get(const std::string& key, const std::string& default_value) const {
auto pair = data.find(key);
if (pair == data.end()) {
- LOG_DEBUG(Common, "key '{}' not found", key);
+ LOG_TRACE(Common, "key '{}' not found", key);
return default_value;
}
@@ -86,7 +85,7 @@ std::string ParamPackage::Get(const std::string& key, const std::string& default
int ParamPackage::Get(const std::string& key, int default_value) const {
auto pair = data.find(key);
if (pair == data.end()) {
- LOG_DEBUG(Common, "key '{}' not found", key);
+ LOG_TRACE(Common, "key '{}' not found", key);
return default_value;
}
@@ -101,7 +100,7 @@ int ParamPackage::Get(const std::string& key, int default_value) const {
float ParamPackage::Get(const std::string& key, float default_value) const {
auto pair = data.find(key);
if (pair == data.end()) {
- LOG_DEBUG(Common, "key {} not found", key);
+ LOG_TRACE(Common, "key {} not found", key);
return default_value;
}
diff --git a/src/common/param_package.h b/src/common/param_package.h
index c13e45479..d7c13cb1f 100644
--- a/src/common/param_package.h
+++ b/src/common/param_package.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/parent_of_member.h b/src/common/parent_of_member.h
index 58c70b0e7..8e03f17d8 100644
--- a/src/common/parent_of_member.h
+++ b/src/common/parent_of_member.h
@@ -1,19 +1,17 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#include "common/assert.h"
-#include "common/common_types.h"
namespace Common {
namespace detail {
template <typename T, size_t Size, size_t Align>
struct TypedStorageImpl {
- std::aligned_storage_t<Size, Align> storage_;
+ alignas(Align) u8 storage_[Size];
};
} // namespace detail
diff --git a/src/common/point.h b/src/common/point.h
index c0a52ad8d..6491856ea 100644
--- a/src/common/point.h
+++ b/src/common/point.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/quaternion.h b/src/common/quaternion.h
index 4d0871eb4..5bb5f2af0 100644
--- a/src/common/quaternion.h
+++ b/src/common/quaternion.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/reader_writer_queue.h b/src/common/reader_writer_queue.h
new file mode 100644
index 000000000..60c41a8cb
--- /dev/null
+++ b/src/common/reader_writer_queue.h
@@ -0,0 +1,940 @@
+// SPDX-FileCopyrightText: 2013-2020 Cameron Desrochers
+// SPDX-License-Identifier: BSD-2-Clause
+
+#pragma once
+
+#include <cassert>
+#include <cstdint>
+#include <cstdlib> // For malloc/free/abort & size_t
+#include <memory>
+#include <new>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+
+#include "common/atomic_helpers.h"
+
+#if __cplusplus > 199711L || _MSC_VER >= 1700 // C++11 or VS2012
+#include <chrono>
+#endif
+
+// A lock-free queue for a single-consumer, single-producer architecture.
+// The queue is also wait-free in the common path (except if more memory
+// needs to be allocated, in which case malloc is called).
+// Allocates memory sparingly, and only once if the original maximum size
+// estimate is never exceeded.
+// Tested on x86/x64 processors, but semantics should be correct for all
+// architectures (given the right implementations in atomicops.h), provided
+// that aligned integer and pointer accesses are naturally atomic.
+// Note that there should only be one consumer thread and producer thread;
+// Switching roles of the threads, or using multiple consecutive threads for
+// one role, is not safe unless properly synchronized.
+// Using the queue exclusively from one thread is fine, though a bit silly.
+
+#ifndef MOODYCAMEL_CACHE_LINE_SIZE
+#define MOODYCAMEL_CACHE_LINE_SIZE 64
+#endif
+
+#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED
+#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || \
+ (!defined(_MSC_VER) && !defined(__GNUC__))
+#define MOODYCAMEL_EXCEPTIONS_ENABLED
+#endif
+#endif
+
+#ifndef MOODYCAMEL_HAS_EMPLACE
+#if !defined(_MSC_VER) || \
+ _MSC_VER >= 1800 // variadic templates: either a non-MS compiler or VS >= 2013
+#define MOODYCAMEL_HAS_EMPLACE 1
+#endif
+#endif
+
+#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE
+#if defined(__APPLE__) && defined(__MACH__) && __cplusplus >= 201703L
+// This is required to find out what deployment target we are using
+#include <CoreFoundation/CoreFoundation.h>
+#if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || \
+ MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14
+// C++17 new(size_t, align_val_t) is not backwards-compatible with older versions of macOS, so we
+// can't support over-alignment in this case
+#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE
+#endif
+#endif
+#endif
+
+#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE
+#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE AE_ALIGN(MOODYCAMEL_CACHE_LINE_SIZE)
+#endif
+
+#ifdef AE_VCPP
+#pragma warning(push)
+#pragma warning(disable : 4324) // structure was padded due to __declspec(align())
+#pragma warning(disable : 4820) // padding was added
+#pragma warning(disable : 4127) // conditional expression is constant
+#endif
+
+namespace Common {
+
+template <typename T, size_t MAX_BLOCK_SIZE = 512>
+class MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE ReaderWriterQueue {
+ // Design: Based on a queue-of-queues. The low-level queues are just
+ // circular buffers with front and tail indices indicating where the
+ // next element to dequeue is and where the next element can be enqueued,
+ // respectively. Each low-level queue is called a "block". Each block
+ // wastes exactly one element's worth of space to keep the design simple
+ // (if front == tail then the queue is empty, and can't be full).
+ // The high-level queue is a circular linked list of blocks; again there
+ // is a front and tail, but this time they are pointers to the blocks.
+ // The front block is where the next element to be dequeued is, provided
+ // the block is not empty. The back block is where elements are to be
+ // enqueued, provided the block is not full.
+ // The producer thread owns all the tail indices/pointers. The consumer
+ // thread owns all the front indices/pointers. Both threads read each
+ // other's variables, but only the owning thread updates them. E.g. After
+ // the consumer reads the producer's tail, the tail may change before the
+ // consumer is done dequeuing an object, but the consumer knows the tail
+ // will never go backwards, only forwards.
+ // If there is no room to enqueue an object, an additional block (of
+ // equal size to the last block) is added. Blocks are never removed.
+
+public:
+ typedef T value_type;
+
+ // Constructs a queue that can hold at least `size` elements without further
+ // allocations. If more than MAX_BLOCK_SIZE elements are requested,
+ // then several blocks of MAX_BLOCK_SIZE each are reserved (including
+ // at least one extra buffer block).
+ AE_NO_TSAN explicit ReaderWriterQueue(size_t size = 15)
+#ifndef NDEBUG
+ : enqueuing(false), dequeuing(false)
+#endif
+ {
+ assert(MAX_BLOCK_SIZE == ceilToPow2(MAX_BLOCK_SIZE) &&
+ "MAX_BLOCK_SIZE must be a power of 2");
+ assert(MAX_BLOCK_SIZE >= 2 && "MAX_BLOCK_SIZE must be at least 2");
+
+ Block* firstBlock = nullptr;
+
+ largestBlockSize =
+ ceilToPow2(size + 1); // We need a spare slot to fit size elements in the block
+ if (largestBlockSize > MAX_BLOCK_SIZE * 2) {
+ // We need a spare block in case the producer is writing to a different block the
+ // consumer is reading from, and wants to enqueue the maximum number of elements. We
+ // also need a spare element in each block to avoid the ambiguity between front == tail
+ // meaning "empty" and "full". So the effective number of slots that are guaranteed to
+ // be usable at any time is the block size - 1 times the number of blocks - 1. Solving
+ // for size and applying a ceiling to the division gives us (after simplifying):
+ size_t initialBlockCount = (size + MAX_BLOCK_SIZE * 2 - 3) / (MAX_BLOCK_SIZE - 1);
+ largestBlockSize = MAX_BLOCK_SIZE;
+ Block* lastBlock = nullptr;
+ for (size_t i = 0; i != initialBlockCount; ++i) {
+ auto block = make_block(largestBlockSize);
+ if (block == nullptr) {
+#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED
+ throw std::bad_alloc();
+#else
+ abort();
+#endif
+ }
+ if (firstBlock == nullptr) {
+ firstBlock = block;
+ } else {
+ lastBlock->next = block;
+ }
+ lastBlock = block;
+ block->next = firstBlock;
+ }
+ } else {
+ firstBlock = make_block(largestBlockSize);
+ if (firstBlock == nullptr) {
+#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED
+ throw std::bad_alloc();
+#else
+ abort();
+#endif
+ }
+ firstBlock->next = firstBlock;
+ }
+ frontBlock = firstBlock;
+ tailBlock = firstBlock;
+
+ // Make sure the reader/writer threads will have the initialized memory setup above:
+ fence(memory_order_sync);
+ }
+
+ // Note: The queue should not be accessed concurrently while it's
+ // being moved. It's up to the user to synchronize this.
+ AE_NO_TSAN ReaderWriterQueue(ReaderWriterQueue&& other)
+ : frontBlock(other.frontBlock.load()), tailBlock(other.tailBlock.load()),
+ largestBlockSize(other.largestBlockSize)
+#ifndef NDEBUG
+ ,
+ enqueuing(false), dequeuing(false)
+#endif
+ {
+ other.largestBlockSize = 32;
+ Block* b = other.make_block(other.largestBlockSize);
+ if (b == nullptr) {
+#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED
+ throw std::bad_alloc();
+#else
+ abort();
+#endif
+ }
+ b->next = b;
+ other.frontBlock = b;
+ other.tailBlock = b;
+ }
+
+ // Note: The queue should not be accessed concurrently while it's
+ // being moved. It's up to the user to synchronize this.
+ ReaderWriterQueue& operator=(ReaderWriterQueue&& other) AE_NO_TSAN {
+ Block* b = frontBlock.load();
+ frontBlock = other.frontBlock.load();
+ other.frontBlock = b;
+ b = tailBlock.load();
+ tailBlock = other.tailBlock.load();
+ other.tailBlock = b;
+ std::swap(largestBlockSize, other.largestBlockSize);
+ return *this;
+ }
+
+ // Note: The queue should not be accessed concurrently while it's
+ // being deleted. It's up to the user to synchronize this.
+ AE_NO_TSAN ~ReaderWriterQueue() {
+ // Make sure we get the latest version of all variables from other CPUs:
+ fence(memory_order_sync);
+
+ // Destroy any remaining objects in queue and free memory
+ Block* frontBlock_ = frontBlock;
+ Block* block = frontBlock_;
+ do {
+ Block* nextBlock = block->next;
+ size_t blockFront = block->front;
+ size_t blockTail = block->tail;
+
+ for (size_t i = blockFront; i != blockTail; i = (i + 1) & block->sizeMask) {
+ auto element = reinterpret_cast<T*>(block->data + i * sizeof(T));
+ element->~T();
+ (void)element;
+ }
+
+ auto rawBlock = block->rawThis;
+ block->~Block();
+ std::free(rawBlock);
+ block = nextBlock;
+ } while (block != frontBlock_);
+ }
+
+ // Enqueues a copy of element if there is room in the queue.
+ // Returns true if the element was enqueued, false otherwise.
+ // Does not allocate memory.
+ AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN {
+ return inner_enqueue<CannotAlloc>(element);
+ }
+
+ // Enqueues a moved copy of element if there is room in the queue.
+ // Returns true if the element was enqueued, false otherwise.
+ // Does not allocate memory.
+ AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN {
+ return inner_enqueue<CannotAlloc>(std::forward<T>(element));
+ }
+
+#if MOODYCAMEL_HAS_EMPLACE
+ // Like try_enqueue() but with emplace semantics (i.e. construct-in-place).
+ template <typename... Args>
+ AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN {
+ return inner_enqueue<CannotAlloc>(std::forward<Args>(args)...);
+ }
+#endif
+
+ // Enqueues a copy of element on the queue.
+ // Allocates an additional block of memory if needed.
+ // Only fails (returns false) if memory allocation fails.
+ AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN {
+ return inner_enqueue<CanAlloc>(element);
+ }
+
+ // Enqueues a moved copy of element on the queue.
+ // Allocates an additional block of memory if needed.
+ // Only fails (returns false) if memory allocation fails.
+ AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN {
+ return inner_enqueue<CanAlloc>(std::forward<T>(element));
+ }
+
+#if MOODYCAMEL_HAS_EMPLACE
+ // Like enqueue() but with emplace semantics (i.e. construct-in-place).
+ template <typename... Args>
+ AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN {
+ return inner_enqueue<CanAlloc>(std::forward<Args>(args)...);
+ }
+#endif
+
+ // Attempts to dequeue an element; if the queue is empty,
+ // returns false instead. If the queue has at least one element,
+ // moves front to result using operator=, then returns true.
+ template <typename U>
+ bool try_dequeue(U& result) AE_NO_TSAN {
+#ifndef NDEBUG
+ ReentrantGuard guard(this->dequeuing);
+#endif
+
+ // High-level pseudocode:
+ // Remember where the tail block is
+ // If the front block has an element in it, dequeue it
+ // Else
+ // If front block was the tail block when we entered the function, return false
+ // Else advance to next block and dequeue the item there
+
+ // Note that we have to use the value of the tail block from before we check if the front
+ // block is full or not, in case the front block is empty and then, before we check if the
+ // tail block is at the front block or not, the producer fills up the front block *and
+ // moves on*, which would make us skip a filled block. Seems unlikely, but was consistently
+ // reproducible in practice.
+ // In order to avoid overhead in the common case, though, we do a double-checked pattern
+ // where we have the fast path if the front block is not empty, then read the tail block,
+ // then re-read the front block and check if it's not empty again, then check if the tail
+ // block has advanced.
+
+ Block* frontBlock_ = frontBlock.load();
+ size_t blockTail = frontBlock_->localTail;
+ size_t blockFront = frontBlock_->front.load();
+
+ if (blockFront != blockTail ||
+ blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {
+ fence(memory_order_acquire);
+
+ non_empty_front_block:
+ // Front block not empty, dequeue from here
+ auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
+ result = std::move(*element);
+ element->~T();
+
+ blockFront = (blockFront + 1) & frontBlock_->sizeMask;
+
+ fence(memory_order_release);
+ frontBlock_->front = blockFront;
+ } else if (frontBlock_ != tailBlock.load()) {
+ fence(memory_order_acquire);
+
+ frontBlock_ = frontBlock.load();
+ blockTail = frontBlock_->localTail = frontBlock_->tail.load();
+ blockFront = frontBlock_->front.load();
+ fence(memory_order_acquire);
+
+ if (blockFront != blockTail) {
+ // Oh look, the front block isn't empty after all
+ goto non_empty_front_block;
+ }
+
+ // Front block is empty but there's another block ahead, advance to it
+ Block* nextBlock = frontBlock_->next;
+ // Don't need an acquire fence here since next can only ever be set on the tailBlock,
+ // and we're not the tailBlock, and we did an acquire earlier after reading tailBlock
+ // which ensures next is up-to-date on this CPU in case we recently were at tailBlock.
+
+ size_t nextBlockFront = nextBlock->front.load();
+ size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load();
+ fence(memory_order_acquire);
+
+ // Since the tailBlock is only ever advanced after being written to,
+ // we know there's for sure an element to dequeue on it
+ assert(nextBlockFront != nextBlockTail);
+ AE_UNUSED(nextBlockTail);
+
+ // We're done with this block, let the producer use it if it needs
+ fence(memory_order_release); // Expose possibly pending changes to frontBlock->front
+ // from last dequeue
+ frontBlock = frontBlock_ = nextBlock;
+
+ compiler_fence(memory_order_release); // Not strictly needed
+
+ auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));
+
+ result = std::move(*element);
+ element->~T();
+
+ nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;
+
+ fence(memory_order_release);
+ frontBlock_->front = nextBlockFront;
+ } else {
+ // No elements in current block and no other block to advance to
+ return false;
+ }
+
+ return true;
+ }
+
+ // Returns a pointer to the front element in the queue (the one that
+ // would be removed next by a call to `try_dequeue` or `pop`). If the
+ // queue appears empty at the time the method is called, nullptr is
+ // returned instead.
+ // Must be called only from the consumer thread.
+ T* peek() const AE_NO_TSAN {
+#ifndef NDEBUG
+ ReentrantGuard guard(this->dequeuing);
+#endif
+ // See try_dequeue() for reasoning
+
+ Block* frontBlock_ = frontBlock.load();
+ size_t blockTail = frontBlock_->localTail;
+ size_t blockFront = frontBlock_->front.load();
+
+ if (blockFront != blockTail ||
+ blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {
+ fence(memory_order_acquire);
+ non_empty_front_block:
+ return reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
+ } else if (frontBlock_ != tailBlock.load()) {
+ fence(memory_order_acquire);
+ frontBlock_ = frontBlock.load();
+ blockTail = frontBlock_->localTail = frontBlock_->tail.load();
+ blockFront = frontBlock_->front.load();
+ fence(memory_order_acquire);
+
+ if (blockFront != blockTail) {
+ goto non_empty_front_block;
+ }
+
+ Block* nextBlock = frontBlock_->next;
+
+ size_t nextBlockFront = nextBlock->front.load();
+ fence(memory_order_acquire);
+
+ assert(nextBlockFront != nextBlock->tail.load());
+ return reinterpret_cast<T*>(nextBlock->data + nextBlockFront * sizeof(T));
+ }
+
+ return nullptr;
+ }
+
+ // Removes the front element from the queue, if any, without returning it.
+ // Returns true on success, or false if the queue appeared empty at the time
+ // `pop` was called.
+ bool pop() AE_NO_TSAN {
+#ifndef NDEBUG
+ ReentrantGuard guard(this->dequeuing);
+#endif
+ // See try_dequeue() for reasoning
+
+ Block* frontBlock_ = frontBlock.load();
+ size_t blockTail = frontBlock_->localTail;
+ size_t blockFront = frontBlock_->front.load();
+
+ if (blockFront != blockTail ||
+ blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {
+ fence(memory_order_acquire);
+
+ non_empty_front_block:
+ auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
+ element->~T();
+
+ blockFront = (blockFront + 1) & frontBlock_->sizeMask;
+
+ fence(memory_order_release);
+ frontBlock_->front = blockFront;
+ } else if (frontBlock_ != tailBlock.load()) {
+ fence(memory_order_acquire);
+ frontBlock_ = frontBlock.load();
+ blockTail = frontBlock_->localTail = frontBlock_->tail.load();
+ blockFront = frontBlock_->front.load();
+ fence(memory_order_acquire);
+
+ if (blockFront != blockTail) {
+ goto non_empty_front_block;
+ }
+
+ // Front block is empty but there's another block ahead, advance to it
+ Block* nextBlock = frontBlock_->next;
+
+ size_t nextBlockFront = nextBlock->front.load();
+ size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load();
+ fence(memory_order_acquire);
+
+ assert(nextBlockFront != nextBlockTail);
+ AE_UNUSED(nextBlockTail);
+
+ fence(memory_order_release);
+ frontBlock = frontBlock_ = nextBlock;
+
+ compiler_fence(memory_order_release);
+
+ auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));
+ element->~T();
+
+ nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;
+
+ fence(memory_order_release);
+ frontBlock_->front = nextBlockFront;
+ } else {
+ // No elements in current block and no other block to advance to
+ return false;
+ }
+
+ return true;
+ }
+
+ // Returns the approximate number of items currently in the queue.
+ // Safe to call from both the producer and consumer threads.
+ inline size_t size_approx() const AE_NO_TSAN {
+ size_t result = 0;
+ Block* frontBlock_ = frontBlock.load();
+ Block* block = frontBlock_;
+ do {
+ fence(memory_order_acquire);
+ size_t blockFront = block->front.load();
+ size_t blockTail = block->tail.load();
+ result += (blockTail - blockFront) & block->sizeMask;
+ block = block->next.load();
+ } while (block != frontBlock_);
+ return result;
+ }
+
+ // Returns the total number of items that could be enqueued without incurring
+ // an allocation when this queue is empty.
+ // Safe to call from both the producer and consumer threads.
+ //
+ // NOTE: The actual capacity during usage may be different depending on the consumer.
+ // If the consumer is removing elements concurrently, the producer cannot add to
+ // the block the consumer is removing from until it's completely empty, except in
+ // the case where the producer was writing to the same block the consumer was
+ // reading from the whole time.
+ inline size_t max_capacity() const {
+ size_t result = 0;
+ Block* frontBlock_ = frontBlock.load();
+ Block* block = frontBlock_;
+ do {
+ fence(memory_order_acquire);
+ result += block->sizeMask;
+ block = block->next.load();
+ } while (block != frontBlock_);
+ return result;
+ }
+
+private:
+ enum AllocationMode { CanAlloc, CannotAlloc };
+
+#if MOODYCAMEL_HAS_EMPLACE
+ template <AllocationMode canAlloc, typename... Args>
+ bool inner_enqueue(Args&&... args) AE_NO_TSAN
+#else
+ template <AllocationMode canAlloc, typename U>
+ bool inner_enqueue(U&& element) AE_NO_TSAN
+#endif
+ {
+#ifndef NDEBUG
+ ReentrantGuard guard(this->enqueuing);
+#endif
+
+ // High-level pseudocode (assuming we're allowed to alloc a new block):
+ // If room in tail block, add to tail
+ // Else check next block
+ // If next block is not the head block, enqueue on next block
+ // Else create a new block and enqueue there
+ // Advance tail to the block we just enqueued to
+
+ Block* tailBlock_ = tailBlock.load();
+ size_t blockFront = tailBlock_->localFront;
+ size_t blockTail = tailBlock_->tail.load();
+
+ size_t nextBlockTail = (blockTail + 1) & tailBlock_->sizeMask;
+ if (nextBlockTail != blockFront ||
+ nextBlockTail != (tailBlock_->localFront = tailBlock_->front.load())) {
+ fence(memory_order_acquire);
+ // This block has room for at least one more element
+ char* location = tailBlock_->data + blockTail * sizeof(T);
+#if MOODYCAMEL_HAS_EMPLACE
+ new (location) T(std::forward<Args>(args)...);
+#else
+ new (location) T(std::forward<U>(element));
+#endif
+
+ fence(memory_order_release);
+ tailBlock_->tail = nextBlockTail;
+ } else {
+ fence(memory_order_acquire);
+ if (tailBlock_->next.load() != frontBlock) {
+ // Note that the reason we can't advance to the frontBlock and start adding new
+ // entries there is because if we did, then dequeue would stay in that block,
+ // eventually reading the new values, instead of advancing to the next full block
+ // (whose values were enqueued first and so should be consumed first).
+
+ fence(memory_order_acquire); // Ensure we get latest writes if we got the latest
+ // frontBlock
+
+ // tailBlock is full, but there's a free block ahead, use it
+ Block* tailBlockNext = tailBlock_->next.load();
+ size_t nextBlockFront = tailBlockNext->localFront = tailBlockNext->front.load();
+ nextBlockTail = tailBlockNext->tail.load();
+ fence(memory_order_acquire);
+
+ // This block must be empty since it's not the head block and we
+ // go through the blocks in a circle
+ assert(nextBlockFront == nextBlockTail);
+ tailBlockNext->localFront = nextBlockFront;
+
+ char* location = tailBlockNext->data + nextBlockTail * sizeof(T);
+#if MOODYCAMEL_HAS_EMPLACE
+ new (location) T(std::forward<Args>(args)...);
+#else
+ new (location) T(std::forward<U>(element));
+#endif
+
+ tailBlockNext->tail = (nextBlockTail + 1) & tailBlockNext->sizeMask;
+
+ fence(memory_order_release);
+ tailBlock = tailBlockNext;
+ } else if (canAlloc == CanAlloc) {
+ // tailBlock is full and there's no free block ahead; create a new block
+ auto newBlockSize =
+ largestBlockSize >= MAX_BLOCK_SIZE ? largestBlockSize : largestBlockSize * 2;
+ auto newBlock = make_block(newBlockSize);
+ if (newBlock == nullptr) {
+ // Could not allocate a block!
+ return false;
+ }
+ largestBlockSize = newBlockSize;
+
+#if MOODYCAMEL_HAS_EMPLACE
+ new (newBlock->data) T(std::forward<Args>(args)...);
+#else
+ new (newBlock->data) T(std::forward<U>(element));
+#endif
+ assert(newBlock->front == 0);
+ newBlock->tail = newBlock->localTail = 1;
+
+ newBlock->next = tailBlock_->next.load();
+ tailBlock_->next = newBlock;
+
+ // Might be possible for the dequeue thread to see the new tailBlock->next
+ // *without* seeing the new tailBlock value, but this is OK since it can't
+ // advance to the next block until tailBlock is set anyway (because the only
+ // case where it could try to read the next is if it's already at the tailBlock,
+ // and it won't advance past tailBlock in any circumstance).
+
+ fence(memory_order_release);
+ tailBlock = newBlock;
+ } else if (canAlloc == CannotAlloc) {
+ // Would have had to allocate a new block to enqueue, but not allowed
+ return false;
+ } else {
+ assert(false && "Should be unreachable code");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Disable copying
+ ReaderWriterQueue(ReaderWriterQueue const&) {}
+
+ // Disable assignment
+ ReaderWriterQueue& operator=(ReaderWriterQueue const&) {}
+
+ AE_FORCEINLINE static size_t ceilToPow2(size_t x) {
+ // From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
+ --x;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ for (size_t i = 1; i < sizeof(size_t); i <<= 1) {
+ x |= x >> (i << 3);
+ }
+ ++x;
+ return x;
+ }
+
+ template <typename U>
+ static AE_FORCEINLINE char* align_for(char* ptr) AE_NO_TSAN {
+ const std::size_t alignment = std::alignment_of<U>::value;
+ return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment;
+ }
+
+private:
+#ifndef NDEBUG
+ struct ReentrantGuard {
+ AE_NO_TSAN ReentrantGuard(weak_atomic<bool>& _inSection) : inSection(_inSection) {
+ assert(!inSection &&
+ "Concurrent (or re-entrant) enqueue or dequeue operation detected (only one "
+ "thread at a time may hold the producer or consumer role)");
+ inSection = true;
+ }
+
+ AE_NO_TSAN ~ReentrantGuard() {
+ inSection = false;
+ }
+
+ private:
+ ReentrantGuard& operator=(ReentrantGuard const&);
+
+ private:
+ weak_atomic<bool>& inSection;
+ };
+#endif
+
+ struct Block {
+ // Avoid false-sharing by putting highly contended variables on their own cache lines
+ weak_atomic<size_t> front; // (Atomic) Elements are read from here
+ size_t localTail; // An uncontended shadow copy of tail, owned by the consumer
+
+ char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) -
+ sizeof(size_t)];
+ weak_atomic<size_t> tail; // (Atomic) Elements are enqueued here
+ size_t localFront;
+
+ char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) -
+ sizeof(size_t)]; // next isn't very contended, but we don't want it on
+ // the same cache line as tail (which is)
+ weak_atomic<Block*> next; // (Atomic)
+
+ char* data; // Contents (on heap) are aligned to T's alignment
+
+ const size_t sizeMask;
+
+ // size must be a power of two (and greater than 0)
+ AE_NO_TSAN Block(size_t const& _size, char* _rawThis, char* _data)
+ : front(0UL), localTail(0), tail(0UL), localFront(0), next(nullptr), data(_data),
+ sizeMask(_size - 1), rawThis(_rawThis) {}
+
+ private:
+ // C4512 - Assignment operator could not be generated
+ Block& operator=(Block const&);
+
+ public:
+ char* rawThis;
+ };
+
+ static Block* make_block(size_t capacity) AE_NO_TSAN {
+ // Allocate enough memory for the block itself, as well as all the elements it will contain
+ auto size = sizeof(Block) + std::alignment_of<Block>::value - 1;
+ size += sizeof(T) * capacity + std::alignment_of<T>::value - 1;
+ auto newBlockRaw = static_cast<char*>(std::malloc(size));
+ if (newBlockRaw == nullptr) {
+ return nullptr;
+ }
+
+ auto newBlockAligned = align_for<Block>(newBlockRaw);
+ auto newBlockData = align_for<T>(newBlockAligned + sizeof(Block));
+ return new (newBlockAligned) Block(capacity, newBlockRaw, newBlockData);
+ }
+
+private:
+ weak_atomic<Block*> frontBlock; // (Atomic) Elements are dequeued from this block
+
+ char cachelineFiller[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<Block*>)];
+ weak_atomic<Block*> tailBlock; // (Atomic) Elements are enqueued to this block
+
+ size_t largestBlockSize;
+
+#ifndef NDEBUG
+ weak_atomic<bool> enqueuing;
+ mutable weak_atomic<bool> dequeuing;
+#endif
+};
+
+// Like ReaderWriterQueue, but also providees blocking operations
+template <typename T, size_t MAX_BLOCK_SIZE = 512>
+class BlockingReaderWriterQueue {
+private:
+ typedef ::Common::ReaderWriterQueue<T, MAX_BLOCK_SIZE> ReaderWriterQueue;
+
+public:
+ explicit BlockingReaderWriterQueue(size_t size = 15) AE_NO_TSAN
+ : inner(size),
+ sema(new spsc_sema::LightweightSemaphore()) {}
+
+ BlockingReaderWriterQueue(BlockingReaderWriterQueue&& other) AE_NO_TSAN
+ : inner(std::move(other.inner)),
+ sema(std::move(other.sema)) {}
+
+ BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue&& other) AE_NO_TSAN {
+ std::swap(sema, other.sema);
+ std::swap(inner, other.inner);
+ return *this;
+ }
+
+ // Enqueues a copy of element if there is room in the queue.
+ // Returns true if the element was enqueued, false otherwise.
+ // Does not allocate memory.
+ AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN {
+ if (inner.try_enqueue(element)) {
+ sema->signal();
+ return true;
+ }
+ return false;
+ }
+
+ // Enqueues a moved copy of element if there is room in the queue.
+ // Returns true if the element was enqueued, false otherwise.
+ // Does not allocate memory.
+ AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN {
+ if (inner.try_enqueue(std::forward<T>(element))) {
+ sema->signal();
+ return true;
+ }
+ return false;
+ }
+
+#if MOODYCAMEL_HAS_EMPLACE
+ // Like try_enqueue() but with emplace semantics (i.e. construct-in-place).
+ template <typename... Args>
+ AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN {
+ if (inner.try_emplace(std::forward<Args>(args)...)) {
+ sema->signal();
+ return true;
+ }
+ return false;
+ }
+#endif
+
+ // Enqueues a copy of element on the queue.
+ // Allocates an additional block of memory if needed.
+ // Only fails (returns false) if memory allocation fails.
+ AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN {
+ if (inner.enqueue(element)) {
+ sema->signal();
+ return true;
+ }
+ return false;
+ }
+
+ // Enqueues a moved copy of element on the queue.
+ // Allocates an additional block of memory if needed.
+ // Only fails (returns false) if memory allocation fails.
+ AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN {
+ if (inner.enqueue(std::forward<T>(element))) {
+ sema->signal();
+ return true;
+ }
+ return false;
+ }
+
+#if MOODYCAMEL_HAS_EMPLACE
+ // Like enqueue() but with emplace semantics (i.e. construct-in-place).
+ template <typename... Args>
+ AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN {
+ if (inner.emplace(std::forward<Args>(args)...)) {
+ sema->signal();
+ return true;
+ }
+ return false;
+ }
+#endif
+
+ // Attempts to dequeue an element; if the queue is empty,
+ // returns false instead. If the queue has at least one element,
+ // moves front to result using operator=, then returns true.
+ template <typename U>
+ bool try_dequeue(U& result) AE_NO_TSAN {
+ if (sema->tryWait()) {
+ bool success = inner.try_dequeue(result);
+ assert(success);
+ AE_UNUSED(success);
+ return true;
+ }
+ return false;
+ }
+
+ // Attempts to dequeue an element; if the queue is empty,
+ // waits until an element is available, then dequeues it.
+ template <typename U>
+ void wait_dequeue(U& result) AE_NO_TSAN {
+ while (!sema->wait())
+ ;
+ bool success = inner.try_dequeue(result);
+ AE_UNUSED(result);
+ assert(success);
+ AE_UNUSED(success);
+ }
+
+ // Attempts to dequeue an element; if the queue is empty,
+ // waits until an element is available up to the specified timeout,
+ // then dequeues it and returns true, or returns false if the timeout
+ // expires before an element can be dequeued.
+ // Using a negative timeout indicates an indefinite timeout,
+ // and is thus functionally equivalent to calling wait_dequeue.
+ template <typename U>
+ bool wait_dequeue_timed(U& result, std::int64_t timeout_usecs) AE_NO_TSAN {
+ if (!sema->wait(timeout_usecs)) {
+ return false;
+ }
+ bool success = inner.try_dequeue(result);
+ AE_UNUSED(result);
+ assert(success);
+ AE_UNUSED(success);
+ return true;
+ }
+
+#if __cplusplus > 199711L || _MSC_VER >= 1700
+ // Attempts to dequeue an element; if the queue is empty,
+ // waits until an element is available up to the specified timeout,
+ // then dequeues it and returns true, or returns false if the timeout
+ // expires before an element can be dequeued.
+ // Using a negative timeout indicates an indefinite timeout,
+ // and is thus functionally equivalent to calling wait_dequeue.
+ template <typename U, typename Rep, typename Period>
+ inline bool wait_dequeue_timed(U& result,
+ std::chrono::duration<Rep, Period> const& timeout) AE_NO_TSAN {
+ return wait_dequeue_timed(
+ result, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
+ }
+#endif
+
+ // Returns a pointer to the front element in the queue (the one that
+ // would be removed next by a call to `try_dequeue` or `pop`). If the
+ // queue appears empty at the time the method is called, nullptr is
+ // returned instead.
+ // Must be called only from the consumer thread.
+ AE_FORCEINLINE T* peek() const AE_NO_TSAN {
+ return inner.peek();
+ }
+
+ // Removes the front element from the queue, if any, without returning it.
+ // Returns true on success, or false if the queue appeared empty at the time
+ // `pop` was called.
+ AE_FORCEINLINE bool pop() AE_NO_TSAN {
+ if (sema->tryWait()) {
+ bool result = inner.pop();
+ assert(result);
+ AE_UNUSED(result);
+ return true;
+ }
+ return false;
+ }
+
+ // Returns the approximate number of items currently in the queue.
+ // Safe to call from both the producer and consumer threads.
+ AE_FORCEINLINE size_t size_approx() const AE_NO_TSAN {
+ return sema->availableApprox();
+ }
+
+ // Returns the total number of items that could be enqueued without incurring
+ // an allocation when this queue is empty.
+ // Safe to call from both the producer and consumer threads.
+ //
+ // NOTE: The actual capacity during usage may be different depending on the consumer.
+ // If the consumer is removing elements concurrently, the producer cannot add to
+ // the block the consumer is removing from until it's completely empty, except in
+ // the case where the producer was writing to the same block the consumer was
+ // reading from the whole time.
+ AE_FORCEINLINE size_t max_capacity() const {
+ return inner.max_capacity();
+ }
+
+private:
+ // Disable copying & assignment
+ BlockingReaderWriterQueue(BlockingReaderWriterQueue const&) {}
+ BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue const&) {}
+
+private:
+ ReaderWriterQueue inner;
+ std::unique_ptr<spsc_sema::LightweightSemaphore> sema;
+};
+
+} // namespace Common
+
+#ifdef AE_VCPP
+#pragma warning(pop)
+#endif
diff --git a/src/common/ring_buffer.h b/src/common/ring_buffer.h
index 4a8d09806..4c328ab44 100644
--- a/src/common/ring_buffer.h
+++ b/src/common/ring_buffer.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -12,7 +11,6 @@
#include <new>
#include <type_traits>
#include <vector>
-#include "common/common_types.h"
namespace Common {
diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in
index cc88994c6..f0c124d69 100644
--- a/src/common/scm_rev.cpp.in
+++ b/src/common/scm_rev.cpp.in
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/scm_rev.h"
diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h
index 563015ec9..88404316a 100644
--- a/src/common/scm_rev.h
+++ b/src/common/scm_rev.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h
index 35dac3a8f..e9c789c88 100644
--- a/src/common/scope_exit.h
+++ b/src/common/scope_exit.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 6964a8273..0a560ebb7 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
@@ -11,7 +10,7 @@
namespace Settings {
-Values values = {};
+Values values;
static bool configuring_global = true;
std::string GetTimeZoneString() {
@@ -63,7 +62,8 @@ void LogSettings() {
log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue());
log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue());
log_setting("Audio_OutputEngine", values.sink_id.GetValue());
- log_setting("Audio_OutputDevice", values.audio_device_id.GetValue());
+ log_setting("Audio_OutputDevice", values.audio_output_device_id.GetValue());
+ log_setting("Audio_InputDevice", values.audio_input_device_id.GetValue());
log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd.GetValue());
log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir));
log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir));
@@ -71,6 +71,7 @@ void LogSettings() {
log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir));
log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
log_setting("Debugging_ProgramArgs", values.program_args.GetValue());
+ log_setting("Debugging_GDBStub", values.use_gdbstub.GetValue());
log_setting("Input_EnableMotion", values.motion_enabled.GetValue());
log_setting("Input_EnableVibration", values.vibration_enabled.GetValue());
log_setting("Input_EnableRawInput", values.enable_raw_input.GetValue());
@@ -104,7 +105,7 @@ float Volume() {
if (values.audio_muted) {
return 0.0f;
}
- return values.volume.GetValue() / 100.0f;
+ return values.volume.GetValue() / static_cast<f32>(values.volume.GetDefault());
}
void UpdateRescalingInfo() {
@@ -147,7 +148,7 @@ void UpdateRescalingInfo() {
info.down_shift = 0;
break;
default:
- UNREACHABLE();
+ ASSERT(false);
info.up_scale = 1;
info.down_shift = 0;
}
@@ -167,6 +168,7 @@ void RestoreGlobalState(bool is_powered_on) {
// Core
values.use_multi_core.SetGlobal(true);
+ values.use_extended_memory_layout.SetGlobal(true);
// CPU
values.cpu_accuracy.SetGlobal(true);
@@ -175,6 +177,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.cpuopt_unsafe_ignore_standard_fpcr.SetGlobal(true);
values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true);
values.cpuopt_unsafe_fastmem_check.SetGlobal(true);
+ values.cpuopt_unsafe_ignore_global_monitor.SetGlobal(true);
// Renderer
values.renderer_backend.SetGlobal(true);
@@ -183,7 +186,6 @@ void RestoreGlobalState(bool is_powered_on) {
values.max_anisotropy.SetGlobal(true);
values.use_speed_limit.SetGlobal(true);
values.speed_limit.SetGlobal(true);
- values.fps_cap.SetGlobal(true);
values.use_disk_shader_cache.SetGlobal(true);
values.gpu_accuracy.SetGlobal(true);
values.use_asynchronous_gpu_emulation.SetGlobal(true);
@@ -193,6 +195,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.shader_backend.SetGlobal(true);
values.use_asynchronous_shaders.SetGlobal(true);
values.use_fast_gpu_time.SetGlobal(true);
+ values.use_pessimistic_flushes.SetGlobal(true);
values.bg_red.SetGlobal(true);
values.bg_green.SetGlobal(true);
values.bg_blue.SetGlobal(true);
diff --git a/src/common/settings.h b/src/common/settings.h
index 9bee6e10f..d2452c93b 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -38,6 +37,7 @@ enum class CPUAccuracy : u32 {
Auto = 0,
Accurate = 1,
Unsafe = 2,
+ Paranoid = 3,
};
enum class FullscreenMode : u32 {
@@ -101,15 +101,15 @@ struct ResolutionScalingInfo {
}
};
-/** The BasicSetting class is a simple resource manager. It defines a label and default value
- * alongside the actual value of the setting for simpler and less-error prone use with frontend
- * configurations. Setting a default value and label is required, though subclasses may deviate from
- * this requirement.
+/** The Setting class is a simple resource manager. It defines a label and default value alongside
+ * the actual value of the setting for simpler and less-error prone use with frontend
+ * configurations. Specifying a default value and label is required. A minimum and maximum range can
+ * be specified for sanitization.
*/
-template <typename Type>
-class BasicSetting {
+template <typename Type, bool ranged = false>
+class Setting {
protected:
- BasicSetting() = default;
+ Setting() = default;
/**
* Only sets the setting to the given initializer, leaving the other members to their default
@@ -117,7 +117,7 @@ protected:
*
* @param global_val Initial value of the setting
*/
- explicit BasicSetting(const Type& global_val) : global{global_val} {}
+ explicit Setting(const Type& val) : value{val} {}
public:
/**
@@ -126,9 +126,22 @@ public:
* @param default_val Intial value of the setting, and default value of the setting
* @param name Label for the setting
*/
- explicit BasicSetting(const Type& default_val, const std::string& name)
- : default_value{default_val}, global{default_val}, label{name} {}
- virtual ~BasicSetting() = default;
+ explicit Setting(const Type& default_val, const std::string& name) requires(!ranged)
+ : value{default_val}, default_value{default_val}, label{name} {}
+ virtual ~Setting() = default;
+
+ /**
+ * Sets a default value, minimum value, maximum value, and label.
+ *
+ * @param default_val Intial value of the setting, and default value of the setting
+ * @param min_val Sets the minimum allowed value of the setting
+ * @param max_val Sets the maximum allowed value of the setting
+ * @param name Label for the setting
+ */
+ explicit Setting(const Type& default_val, const Type& min_val, const Type& max_val,
+ const std::string& name) requires(ranged)
+ : value{default_val},
+ default_value{default_val}, maximum{max_val}, minimum{min_val}, label{name} {}
/**
* Returns a reference to the setting's value.
@@ -136,17 +149,17 @@ public:
* @returns A reference to the setting
*/
[[nodiscard]] virtual const Type& GetValue() const {
- return global;
+ return value;
}
/**
* Sets the setting to the given value.
*
- * @param value The desired value
+ * @param val The desired value
*/
- virtual void SetValue(const Type& value) {
- Type temp{value};
- std::swap(global, temp);
+ virtual void SetValue(const Type& val) {
+ Type temp{ranged ? std::clamp(val, minimum, maximum) : val};
+ std::swap(value, temp);
}
/**
@@ -170,14 +183,14 @@ public:
/**
* Assigns a value to the setting.
*
- * @param value The desired setting value
+ * @param val The desired setting value
*
* @returns A reference to the setting
*/
- virtual const Type& operator=(const Type& value) {
- Type temp{value};
- std::swap(global, temp);
- return global;
+ virtual const Type& operator=(const Type& val) {
+ Type temp{ranged ? std::clamp(val, minimum, maximum) : val};
+ std::swap(value, temp);
+ return value;
}
/**
@@ -186,72 +199,27 @@ public:
* @returns A reference to the setting
*/
explicit virtual operator const Type&() const {
- return global;
+ return value;
}
protected:
+ Type value{}; ///< The setting
const Type default_value{}; ///< The default value
- Type global{}; ///< The setting
+ const Type maximum{}; ///< Maximum allowed value of the setting
+ const Type minimum{}; ///< Minimum allowed value of the setting
const std::string label{}; ///< The setting's label
};
/**
- * BasicRangedSetting class is intended for use with quantifiable settings that need a more
- * restrictive range than implicitly defined by its type. Implements a minimum and maximum that is
- * simply used to sanitize SetValue and the assignment overload.
- */
-template <typename Type>
-class BasicRangedSetting : virtual public BasicSetting<Type> {
-public:
- /**
- * Sets a default value, minimum value, maximum value, and label.
- *
- * @param default_val Intial value of the setting, and default value of the setting
- * @param min_val Sets the minimum allowed value of the setting
- * @param max_val Sets the maximum allowed value of the setting
- * @param name Label for the setting
- */
- explicit BasicRangedSetting(const Type& default_val, const Type& min_val, const Type& max_val,
- const std::string& name)
- : BasicSetting<Type>{default_val, name}, minimum{min_val}, maximum{max_val} {}
- virtual ~BasicRangedSetting() = default;
-
- /**
- * Like BasicSetting's SetValue, except value is clamped to the range of the setting.
- *
- * @param value The desired value
- */
- void SetValue(const Type& value) override {
- this->global = std::clamp(value, minimum, maximum);
- }
-
- /**
- * Like BasicSetting's assignment overload, except value is clamped to the range of the setting.
- *
- * @param value The desired value
- * @returns A reference to the setting's value
- */
- const Type& operator=(const Type& value) override {
- this->global = std::clamp(value, minimum, maximum);
- return this->global;
- }
-
- const Type minimum; ///< Minimum allowed value of the setting
- const Type maximum; ///< Maximum allowed value of the setting
-};
-
-/**
- * The Setting class is a slightly more complex version of the BasicSetting class. This adds a
+ * The SwitchableSetting class is a slightly more complex version of the Setting class. This adds a
* custom setting to switch to when a guest application specifically requires it. The effect is that
* other components of the emulator can access the setting's intended value without any need for the
* component to ask whether the custom or global setting is needed at the moment.
*
* By default, the global setting is used.
- *
- * Like the BasicSetting, this requires setting a default value and label to use.
*/
-template <typename Type>
-class Setting : virtual public BasicSetting<Type> {
+template <typename Type, bool ranged = false>
+class SwitchableSetting : virtual public Setting<Type, ranged> {
public:
/**
* Sets a default value, label, and setting value.
@@ -259,9 +227,21 @@ public:
* @param default_val Intial value of the setting, and default value of the setting
* @param name Label for the setting
*/
- explicit Setting(const Type& default_val, const std::string& name)
- : BasicSetting<Type>(default_val, name) {}
- virtual ~Setting() = default;
+ explicit SwitchableSetting(const Type& default_val, const std::string& name) requires(!ranged)
+ : Setting<Type>{default_val, name} {}
+ virtual ~SwitchableSetting() = default;
+
+ /**
+ * Sets a default value, minimum value, maximum value, and label.
+ *
+ * @param default_val Intial value of the setting, and default value of the setting
+ * @param min_val Sets the minimum allowed value of the setting
+ * @param max_val Sets the maximum allowed value of the setting
+ * @param name Label for the setting
+ */
+ explicit SwitchableSetting(const Type& default_val, const Type& min_val, const Type& max_val,
+ const std::string& name) requires(ranged)
+ : Setting<Type, true>{default_val, min_val, max_val, name} {}
/**
* Tells this setting to represent either the global or custom setting when other member
@@ -292,13 +272,13 @@ public:
*/
[[nodiscard]] virtual const Type& GetValue() const override {
if (use_global) {
- return this->global;
+ return this->value;
}
return custom;
}
[[nodiscard]] virtual const Type& GetValue(bool need_global) const {
if (use_global || need_global) {
- return this->global;
+ return this->value;
}
return custom;
}
@@ -306,12 +286,12 @@ public:
/**
* Sets the current setting value depending on the global state.
*
- * @param value The new value
+ * @param val The new value
*/
- void SetValue(const Type& value) override {
- Type temp{value};
+ void SetValue(const Type& val) override {
+ Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val};
if (use_global) {
- std::swap(this->global, temp);
+ std::swap(this->value, temp);
} else {
std::swap(custom, temp);
}
@@ -320,15 +300,15 @@ public:
/**
* Assigns the current setting value depending on the global state.
*
- * @param value The new value
+ * @param val The new value
*
* @returns A reference to the current setting value
*/
- const Type& operator=(const Type& value) override {
- Type temp{value};
+ const Type& operator=(const Type& val) override {
+ Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val};
if (use_global) {
- std::swap(this->global, temp);
- return this->global;
+ std::swap(this->value, temp);
+ return this->value;
}
std::swap(custom, temp);
return custom;
@@ -341,7 +321,7 @@ public:
*/
virtual explicit operator const Type&() const override {
if (use_global) {
- return this->global;
+ return this->value;
}
return custom;
}
@@ -352,75 +332,6 @@ protected:
};
/**
- * RangedSetting is a Setting that implements a maximum and minimum value for its setting. Intended
- * for use with quantifiable settings.
- */
-template <typename Type>
-class RangedSetting final : public BasicRangedSetting<Type>, public Setting<Type> {
-public:
- /**
- * Sets a default value, minimum value, maximum value, and label.
- *
- * @param default_val Intial value of the setting, and default value of the setting
- * @param min_val Sets the minimum allowed value of the setting
- * @param max_val Sets the maximum allowed value of the setting
- * @param name Label for the setting
- */
- explicit RangedSetting(const Type& default_val, const Type& min_val, const Type& max_val,
- const std::string& name)
- : BasicSetting<Type>{default_val, name},
- BasicRangedSetting<Type>{default_val, min_val, max_val, name}, Setting<Type>{default_val,
- name} {}
- virtual ~RangedSetting() = default;
-
- // The following are needed to avoid a MSVC bug
- // (source: https://stackoverflow.com/questions/469508)
- [[nodiscard]] const Type& GetValue() const override {
- return Setting<Type>::GetValue();
- }
- [[nodiscard]] const Type& GetValue(bool need_global) const override {
- return Setting<Type>::GetValue(need_global);
- }
- explicit operator const Type&() const override {
- if (this->use_global) {
- return this->global;
- }
- return this->custom;
- }
-
- /**
- * Like BasicSetting's SetValue, except value is clamped to the range of the setting. Sets the
- * appropriate value depending on the global state.
- *
- * @param value The desired value
- */
- void SetValue(const Type& value) override {
- const Type temp = std::clamp(value, this->minimum, this->maximum);
- if (this->use_global) {
- this->global = temp;
- }
- this->custom = temp;
- }
-
- /**
- * Like BasicSetting's assignment overload, except value is clamped to the range of the setting.
- * Uses the appropriate value depending on the global state.
- *
- * @param value The desired value
- * @returns A reference to the setting's value
- */
- const Type& operator=(const Type& value) override {
- const Type temp = std::clamp(value, this->minimum, this->maximum);
- if (this->use_global) {
- this->global = temp;
- return this->global;
- }
- this->custom = temp;
- return this->custom;
- }
-};
-
-/**
* The InputSetting class allows for getting a reference to either the global or custom members.
* This is required as we cannot easily modify the values of user-defined types within containers
* using the SetValue() member function found in the Setting class. The primary purpose of this
@@ -431,7 +342,7 @@ template <typename Type>
class InputSetting final {
public:
InputSetting() = default;
- explicit InputSetting(Type val) : BasicSetting<Type>(val) {}
+ explicit InputSetting(Type val) : Setting<Type>(val) {}
~InputSetting() = default;
void SetGlobal(bool to_global) {
use_global = to_global;
@@ -459,167 +370,181 @@ struct TouchFromButtonMap {
struct Values {
// Audio
- BasicSetting<std::string> audio_device_id{"auto", "output_device"};
- BasicSetting<std::string> sink_id{"auto", "output_engine"};
- BasicSetting<bool> audio_muted{false, "audio_muted"};
- RangedSetting<u8> volume{100, 0, 100, "volume"};
+ Setting<std::string> sink_id{"auto", "output_engine"};
+ Setting<std::string> audio_output_device_id{"auto", "output_device"};
+ Setting<std::string> audio_input_device_id{"auto", "input_device"};
+ Setting<bool> audio_muted{false, "audio_muted"};
+ SwitchableSetting<u8, true> volume{100, 0, 200, "volume"};
+ Setting<bool> dump_audio_commands{false, "dump_audio_commands"};
// Core
- Setting<bool> use_multi_core{true, "use_multi_core"};
+ SwitchableSetting<bool> use_multi_core{true, "use_multi_core"};
+ SwitchableSetting<bool> use_extended_memory_layout{false, "use_extended_memory_layout"};
// Cpu
- RangedSetting<CPUAccuracy> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto,
- CPUAccuracy::Unsafe, "cpu_accuracy"};
+ SwitchableSetting<CPUAccuracy, true> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto,
+ 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"};
-
- BasicSetting<bool> cpuopt_page_tables{true, "cpuopt_page_tables"};
- BasicSetting<bool> cpuopt_block_linking{true, "cpuopt_block_linking"};
- BasicSetting<bool> cpuopt_return_stack_buffer{true, "cpuopt_return_stack_buffer"};
- BasicSetting<bool> cpuopt_fast_dispatcher{true, "cpuopt_fast_dispatcher"};
- BasicSetting<bool> cpuopt_context_elimination{true, "cpuopt_context_elimination"};
- BasicSetting<bool> cpuopt_const_prop{true, "cpuopt_const_prop"};
- BasicSetting<bool> cpuopt_misc_ir{true, "cpuopt_misc_ir"};
- BasicSetting<bool> cpuopt_reduce_misalign_checks{true, "cpuopt_reduce_misalign_checks"};
- BasicSetting<bool> cpuopt_fastmem{true, "cpuopt_fastmem"};
-
- Setting<bool> cpuopt_unsafe_unfuse_fma{true, "cpuopt_unsafe_unfuse_fma"};
- Setting<bool> cpuopt_unsafe_reduce_fp_error{true, "cpuopt_unsafe_reduce_fp_error"};
- Setting<bool> cpuopt_unsafe_ignore_standard_fpcr{true, "cpuopt_unsafe_ignore_standard_fpcr"};
- Setting<bool> cpuopt_unsafe_inaccurate_nan{true, "cpuopt_unsafe_inaccurate_nan"};
- Setting<bool> cpuopt_unsafe_fastmem_check{true, "cpuopt_unsafe_fastmem_check"};
+ Setting<bool> cpu_accuracy_first_time{true, "cpu_accuracy_first_time"};
+ Setting<bool> cpu_debug_mode{false, "cpu_debug_mode"};
+
+ Setting<bool> cpuopt_page_tables{true, "cpuopt_page_tables"};
+ Setting<bool> cpuopt_block_linking{true, "cpuopt_block_linking"};
+ Setting<bool> cpuopt_return_stack_buffer{true, "cpuopt_return_stack_buffer"};
+ Setting<bool> cpuopt_fast_dispatcher{true, "cpuopt_fast_dispatcher"};
+ Setting<bool> cpuopt_context_elimination{true, "cpuopt_context_elimination"};
+ Setting<bool> cpuopt_const_prop{true, "cpuopt_const_prop"};
+ Setting<bool> cpuopt_misc_ir{true, "cpuopt_misc_ir"};
+ Setting<bool> cpuopt_reduce_misalign_checks{true, "cpuopt_reduce_misalign_checks"};
+ Setting<bool> cpuopt_fastmem{true, "cpuopt_fastmem"};
+ Setting<bool> cpuopt_fastmem_exclusives{true, "cpuopt_fastmem_exclusives"};
+ Setting<bool> cpuopt_recompile_exclusives{true, "cpuopt_recompile_exclusives"};
+
+ SwitchableSetting<bool> cpuopt_unsafe_unfuse_fma{true, "cpuopt_unsafe_unfuse_fma"};
+ SwitchableSetting<bool> cpuopt_unsafe_reduce_fp_error{true, "cpuopt_unsafe_reduce_fp_error"};
+ SwitchableSetting<bool> cpuopt_unsafe_ignore_standard_fpcr{
+ true, "cpuopt_unsafe_ignore_standard_fpcr"};
+ SwitchableSetting<bool> cpuopt_unsafe_inaccurate_nan{true, "cpuopt_unsafe_inaccurate_nan"};
+ SwitchableSetting<bool> cpuopt_unsafe_fastmem_check{true, "cpuopt_unsafe_fastmem_check"};
+ SwitchableSetting<bool> cpuopt_unsafe_ignore_global_monitor{
+ true, "cpuopt_unsafe_ignore_global_monitor"};
// Renderer
- RangedSetting<RendererBackend> renderer_backend{
- RendererBackend::OpenGL, RendererBackend::OpenGL, RendererBackend::Vulkan, "backend"};
- BasicSetting<bool> renderer_debug{false, "debug"};
- BasicSetting<bool> renderer_shader_feedback{false, "shader_feedback"};
- BasicSetting<bool> enable_nsight_aftermath{false, "nsight_aftermath"};
- BasicSetting<bool> disable_shader_loop_safety_checks{false,
- "disable_shader_loop_safety_checks"};
- Setting<int> vulkan_device{0, "vulkan_device"};
+ SwitchableSetting<RendererBackend, true> renderer_backend{
+ RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Vulkan, "backend"};
+ Setting<bool> renderer_debug{false, "debug"};
+ Setting<bool> renderer_shader_feedback{false, "shader_feedback"};
+ Setting<bool> enable_nsight_aftermath{false, "nsight_aftermath"};
+ Setting<bool> disable_shader_loop_safety_checks{false, "disable_shader_loop_safety_checks"};
+ SwitchableSetting<int> vulkan_device{0, "vulkan_device"};
ResolutionScalingInfo resolution_info{};
- Setting<ResolutionSetup> resolution_setup{ResolutionSetup::Res1X, "resolution_setup"};
- Setting<ScalingFilter> scaling_filter{ScalingFilter::Bilinear, "scaling_filter"};
- Setting<AntiAliasing> anti_aliasing{AntiAliasing::None, "anti_aliasing"};
+ SwitchableSetting<ResolutionSetup> resolution_setup{ResolutionSetup::Res1X, "resolution_setup"};
+ SwitchableSetting<ScalingFilter> scaling_filter{ScalingFilter::Bilinear, "scaling_filter"};
+ SwitchableSetting<AntiAliasing> anti_aliasing{AntiAliasing::None, "anti_aliasing"};
// *nix platforms may have issues with the borderless windowed fullscreen mode.
// Default to exclusive fullscreen on these platforms for now.
- RangedSetting<FullscreenMode> fullscreen_mode{
+ SwitchableSetting<FullscreenMode, true> fullscreen_mode{
#ifdef _WIN32
FullscreenMode::Borderless,
#else
FullscreenMode::Exclusive,
#endif
FullscreenMode::Borderless, FullscreenMode::Exclusive, "fullscreen_mode"};
- RangedSetting<int> aspect_ratio{0, 0, 3, "aspect_ratio"};
- RangedSetting<int> max_anisotropy{0, 0, 5, "max_anisotropy"};
- Setting<bool> use_speed_limit{true, "use_speed_limit"};
- RangedSetting<u16> speed_limit{100, 0, 9999, "speed_limit"};
- Setting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"};
- RangedSetting<GPUAccuracy> gpu_accuracy{GPUAccuracy::High, GPUAccuracy::Normal,
- GPUAccuracy::Extreme, "gpu_accuracy"};
- Setting<bool> use_asynchronous_gpu_emulation{true, "use_asynchronous_gpu_emulation"};
- Setting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"};
- Setting<bool> accelerate_astc{true, "accelerate_astc"};
- Setting<bool> use_vsync{true, "use_vsync"};
- RangedSetting<u16> fps_cap{1000, 1, 1000, "fps_cap"};
- BasicSetting<bool> disable_fps_limit{false, "disable_fps_limit"};
- RangedSetting<ShaderBackend> shader_backend{ShaderBackend::GLASM, ShaderBackend::GLSL,
- ShaderBackend::SPIRV, "shader_backend"};
- Setting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
- Setting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"};
-
- Setting<u8> bg_red{0, "bg_red"};
- Setting<u8> bg_green{0, "bg_green"};
- Setting<u8> bg_blue{0, "bg_blue"};
+ SwitchableSetting<int, true> aspect_ratio{0, 0, 3, "aspect_ratio"};
+ SwitchableSetting<int, true> max_anisotropy{0, 0, 5, "max_anisotropy"};
+ SwitchableSetting<bool> use_speed_limit{true, "use_speed_limit"};
+ SwitchableSetting<u16, true> speed_limit{100, 0, 9999, "speed_limit"};
+ SwitchableSetting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"};
+ SwitchableSetting<GPUAccuracy, true> gpu_accuracy{GPUAccuracy::High, GPUAccuracy::Normal,
+ GPUAccuracy::Extreme, "gpu_accuracy"};
+ SwitchableSetting<bool> use_asynchronous_gpu_emulation{true, "use_asynchronous_gpu_emulation"};
+ SwitchableSetting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"};
+ SwitchableSetting<bool> accelerate_astc{true, "accelerate_astc"};
+ SwitchableSetting<bool> use_vsync{true, "use_vsync"};
+ SwitchableSetting<ShaderBackend, true> shader_backend{ShaderBackend::GLASM, ShaderBackend::GLSL,
+ ShaderBackend::SPIRV, "shader_backend"};
+ SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
+ SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"};
+ SwitchableSetting<bool> use_pessimistic_flushes{false, "use_pessimistic_flushes"};
+
+ SwitchableSetting<u8> bg_red{0, "bg_red"};
+ SwitchableSetting<u8> bg_green{0, "bg_green"};
+ SwitchableSetting<u8> bg_blue{0, "bg_blue"};
// System
- Setting<std::optional<u32>> rng_seed{std::optional<u32>(), "rng_seed"};
+ SwitchableSetting<std::optional<u32>> rng_seed{std::optional<u32>(), "rng_seed"};
// Measured in seconds since epoch
std::optional<s64> custom_rtc;
// Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc`
s64 custom_rtc_differential;
- BasicSetting<s32> current_user{0, "current_user"};
- RangedSetting<s32> language_index{1, 0, 17, "language_index"};
- RangedSetting<s32> region_index{1, 0, 6, "region_index"};
- RangedSetting<s32> time_zone_index{0, 0, 45, "time_zone_index"};
- RangedSetting<s32> sound_index{1, 0, 2, "sound_index"};
+ Setting<s32> current_user{0, "current_user"};
+ SwitchableSetting<s32, true> language_index{1, 0, 17, "language_index"};
+ SwitchableSetting<s32, true> region_index{1, 0, 6, "region_index"};
+ SwitchableSetting<s32, true> time_zone_index{0, 0, 45, "time_zone_index"};
+ SwitchableSetting<s32, true> sound_index{1, 0, 2, "sound_index"};
// Controls
InputSetting<std::array<PlayerInput, 10>> players;
- Setting<bool> use_docked_mode{true, "use_docked_mode"};
+ SwitchableSetting<bool> use_docked_mode{true, "use_docked_mode"};
- BasicSetting<bool> enable_raw_input{false, "enable_raw_input"};
- BasicSetting<bool> controller_navigation{true, "controller_navigation"};
+ Setting<bool> enable_raw_input{false, "enable_raw_input"};
+ Setting<bool> controller_navigation{true, "controller_navigation"};
- Setting<bool> vibration_enabled{true, "vibration_enabled"};
- Setting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
+ SwitchableSetting<bool> vibration_enabled{true, "vibration_enabled"};
+ SwitchableSetting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
- Setting<bool> motion_enabled{true, "motion_enabled"};
- BasicSetting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"};
- BasicSetting<bool> enable_udp_controller{false, "enable_udp_controller"};
+ SwitchableSetting<bool> motion_enabled{true, "motion_enabled"};
+ Setting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"};
+ Setting<bool> enable_udp_controller{false, "enable_udp_controller"};
- BasicSetting<bool> pause_tas_on_load{true, "pause_tas_on_load"};
- BasicSetting<bool> tas_enable{false, "tas_enable"};
- BasicSetting<bool> tas_loop{false, "tas_loop"};
+ Setting<bool> pause_tas_on_load{true, "pause_tas_on_load"};
+ Setting<bool> tas_enable{false, "tas_enable"};
+ Setting<bool> tas_loop{false, "tas_loop"};
- BasicSetting<bool> mouse_panning{false, "mouse_panning"};
- BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"};
- BasicSetting<bool> mouse_enabled{false, "mouse_enabled"};
+ Setting<bool> mouse_panning{false, "mouse_panning"};
+ Setting<u8, true> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"};
+ Setting<bool> mouse_enabled{false, "mouse_enabled"};
- BasicSetting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
- BasicSetting<bool> keyboard_enabled{false, "keyboard_enabled"};
+ Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
+ Setting<bool> keyboard_enabled{false, "keyboard_enabled"};
- BasicSetting<bool> debug_pad_enabled{false, "debug_pad_enabled"};
+ Setting<bool> debug_pad_enabled{false, "debug_pad_enabled"};
ButtonsRaw debug_pad_buttons;
AnalogsRaw debug_pad_analogs;
TouchscreenInput touchscreen;
- BasicSetting<std::string> touch_device{"min_x:100,min_y:50,max_x:1800,max_y:850",
- "touch_device"};
- BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
+ Setting<std::string> touch_device{"min_x:100,min_y:50,max_x:1800,max_y:850", "touch_device"};
+ Setting<int> touch_from_button_map_index{0, "touch_from_button_map"};
std::vector<TouchFromButtonMap> touch_from_button_maps;
+ Setting<bool> enable_ring_controller{true, "enable_ring_controller"};
+ RingconRaw ringcon_analogs;
+
+ Setting<bool> enable_ir_sensor{false, "enable_ir_sensor"};
+ Setting<std::string> ir_sensor_device{"auto", "ir_sensor_device"};
+
// Data Storage
- BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
- BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};
- BasicSetting<bool> gamecard_current_game{false, "gamecard_current_game"};
- BasicSetting<std::string> gamecard_path{std::string(), "gamecard_path"};
+ Setting<bool> use_virtual_sd{true, "use_virtual_sd"};
+ Setting<bool> gamecard_inserted{false, "gamecard_inserted"};
+ Setting<bool> gamecard_current_game{false, "gamecard_current_game"};
+ Setting<std::string> gamecard_path{std::string(), "gamecard_path"};
// Debugging
bool record_frame_times;
- BasicSetting<bool> use_gdbstub{false, "use_gdbstub"};
- BasicSetting<u16> gdbstub_port{0, "gdbstub_port"};
- BasicSetting<std::string> program_args{std::string(), "program_args"};
- BasicSetting<bool> dump_exefs{false, "dump_exefs"};
- BasicSetting<bool> dump_nso{false, "dump_nso"};
- BasicSetting<bool> dump_shaders{false, "dump_shaders"};
- BasicSetting<bool> enable_fs_access_log{false, "enable_fs_access_log"};
- BasicSetting<bool> reporting_services{false, "reporting_services"};
- BasicSetting<bool> quest_flag{false, "quest_flag"};
- BasicSetting<bool> disable_macro_jit{false, "disable_macro_jit"};
- BasicSetting<bool> extended_logging{false, "extended_logging"};
- BasicSetting<bool> use_debug_asserts{false, "use_debug_asserts"};
- BasicSetting<bool> use_auto_stub{false, "use_auto_stub"};
- BasicSetting<bool> enable_all_controllers{false, "enable_all_controllers"};
+ Setting<bool> use_gdbstub{false, "use_gdbstub"};
+ Setting<u16> gdbstub_port{6543, "gdbstub_port"};
+ Setting<std::string> program_args{std::string(), "program_args"};
+ Setting<bool> dump_exefs{false, "dump_exefs"};
+ Setting<bool> dump_nso{false, "dump_nso"};
+ Setting<bool> dump_shaders{false, "dump_shaders"};
+ Setting<bool> dump_macros{false, "dump_macros"};
+ Setting<bool> enable_fs_access_log{false, "enable_fs_access_log"};
+ Setting<bool> reporting_services{false, "reporting_services"};
+ Setting<bool> quest_flag{false, "quest_flag"};
+ Setting<bool> disable_macro_jit{false, "disable_macro_jit"};
+ Setting<bool> extended_logging{false, "extended_logging"};
+ Setting<bool> use_debug_asserts{false, "use_debug_asserts"};
+ Setting<bool> use_auto_stub{false, "use_auto_stub"};
+ Setting<bool> enable_all_controllers{false, "enable_all_controllers"};
+ Setting<bool> create_crash_dumps{false, "create_crash_dumps"};
+ Setting<bool> perform_vulkan_check{true, "perform_vulkan_check"};
// Miscellaneous
- BasicSetting<std::string> log_filter{"*:Info", "log_filter"};
- BasicSetting<bool> use_dev_keys{false, "use_dev_keys"};
+ Setting<std::string> log_filter{"*:Info", "log_filter"};
+ Setting<bool> use_dev_keys{false, "use_dev_keys"};
// Network
- BasicSetting<std::string> network_interface{std::string(), "network_interface"};
+ Setting<std::string> network_interface{std::string(), "network_interface"};
// WebService
- BasicSetting<bool> enable_telemetry{true, "enable_telemetry"};
- BasicSetting<std::string> web_api_url{"https://api.yuzu-emu.org", "web_api_url"};
- BasicSetting<std::string> yuzu_username{std::string(), "yuzu_username"};
- BasicSetting<std::string> yuzu_token{std::string(), "yuzu_token"};
+ Setting<bool> enable_telemetry{true, "enable_telemetry"};
+ Setting<std::string> web_api_url{"https://api.yuzu-emu.org", "web_api_url"};
+ Setting<std::string> yuzu_username{std::string(), "yuzu_username"};
+ Setting<std::string> yuzu_token{std::string(), "yuzu_token"};
// Add-Ons
std::map<u64, std::vector<std::string>> disabled_addons;
diff --git a/src/common/settings_input.cpp b/src/common/settings_input.cpp
index bea2b837b..0a6eea3cf 100644
--- a/src/common/settings_input.cpp
+++ b/src/common/settings_input.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings_input.h"
diff --git a/src/common/settings_input.h b/src/common/settings_input.h
index 4ff37e186..485e4ad22 100644
--- a/src/common/settings_input.h
+++ b/src/common/settings_input.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -357,6 +356,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/socket_types.h b/src/common/socket_types.h
new file mode 100644
index 000000000..0a801a443
--- /dev/null
+++ b/src/common/socket_types.h
@@ -0,0 +1,51 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Network {
+
+/// Address families
+enum class Domain : u8 {
+ INET, ///< Address family for IPv4
+};
+
+/// Socket types
+enum class Type {
+ STREAM,
+ DGRAM,
+ RAW,
+ SEQPACKET,
+};
+
+/// Protocol values for sockets
+enum class Protocol : u8 {
+ ICMP,
+ TCP,
+ UDP,
+};
+
+/// Shutdown mode
+enum class ShutdownHow {
+ RD,
+ WR,
+ RDWR,
+};
+
+/// Array of IPv4 address
+using IPv4Address = std::array<u8, 4>;
+
+/// Cross-platform sockaddr structure
+struct SockAddrIn {
+ Domain family;
+ IPv4Address ip;
+ u16 portno;
+};
+
+constexpr u32 FLAG_MSG_PEEK = 0x2;
+constexpr u32 FLAG_MSG_DONTWAIT = 0x80;
+constexpr u32 FLAG_O_NONBLOCK = 0x800;
+
+} // namespace Network
diff --git a/src/common/spin_lock.cpp b/src/common/spin_lock.cpp
index c1524220f..b2ef4ea1d 100644
--- a/src/common/spin_lock.cpp
+++ b/src/common/spin_lock.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/spin_lock.h"
diff --git a/src/common/spin_lock.h b/src/common/spin_lock.h
index 06ac2f5bb..a83274851 100644
--- a/src/common/spin_lock.h
+++ b/src/common/spin_lock.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/stream.cpp b/src/common/stream.cpp
index bf0496c26..80ddd68c8 100644
--- a/src/common/stream.cpp
+++ b/src/common/stream.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <stdexcept>
#include "common/common_types.h"
diff --git a/src/common/stream.h b/src/common/stream.h
index 0e40692de..5bb26e883 100644
--- a/src/common/stream.h
+++ b/src/common/stream.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index 662171138..7a495bc79 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -1,15 +1,13 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cctype>
#include <codecvt>
-#include <cstdlib>
#include <locale>
#include <sstream>
-#include "common/logging/log.h"
#include "common/string_util.h"
#ifdef _WIN32
@@ -180,6 +178,10 @@ std::wstring UTF8ToUTF16W(const std::string& input) {
#endif
+std::u16string U16StringFromBuffer(const u16* input, std::size_t length) {
+ return std::u16string(reinterpret_cast<const char16_t*>(input), length);
+}
+
std::string StringFromFixedZeroTerminatedBuffer(std::string_view buffer, std::size_t max_len) {
std::size_t len = 0;
while (len < buffer.length() && len < max_len && buffer[len] != '\0') {
diff --git a/src/common/string_util.h b/src/common/string_util.h
index f0dd632ee..ce18a33cf 100644
--- a/src/common/string_util.h
+++ b/src/common/string_util.h
@@ -1,6 +1,6 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -44,6 +44,8 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
#endif
+[[nodiscard]] std::u16string U16StringFromBuffer(const u16* input, std::size_t length);
+
/**
* Compares the string defined by the range [`begin`, `end`) to the null-terminated C-string
* `other` for equality.
diff --git a/src/common/swap.h b/src/common/swap.h
index a80e191dc..037b82781 100644
--- a/src/common/swap.h
+++ b/src/common/swap.h
@@ -1,16 +1,6 @@
-// Copyright (c) 2012- PPSSPP Project / Dolphin Project.
-
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 2.0 or later versions.
-
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License 2.0 for more details.
-
-// A copy of the GPL 2.0 should have been included with the program.
-// If not, see http://www.gnu.org/licenses/
+// SPDX-FileCopyrightText: 2012 PPSSPP Project
+// SPDX-FileCopyrightText: 2012 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
diff --git a/src/common/telemetry.cpp b/src/common/telemetry.cpp
index 6241d08b3..d26394359 100644
--- a/src/common/telemetry.cpp
+++ b/src/common/telemetry.cpp
@@ -1,10 +1,8 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
-#include "common/assert.h"
#include "common/scm_rev.h"
#include "common/telemetry.h"
@@ -55,22 +53,50 @@ void AppendBuildInfo(FieldCollection& fc) {
void AppendCPUInfo(FieldCollection& fc) {
#ifdef ARCHITECTURE_x86_64
- fc.AddField(FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string);
- fc.AddField(FieldType::UserSystem, "CPU_BrandString", Common::GetCPUCaps().brand_string);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX512", Common::GetCPUCaps().avx512);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA4", Common::GetCPUCaps().fma4);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE", Common::GetCPUCaps().sse);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE2", Common::GetCPUCaps().sse2);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE3", Common::GetCPUCaps().sse3);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSSE3", Common::GetCPUCaps().ssse3);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE41", Common::GetCPUCaps().sse4_1);
- fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE42", Common::GetCPUCaps().sse4_2);
+
+ const auto& caps = Common::GetCPUCaps();
+ const auto add_field = [&fc](std::string_view field_name, const auto& field_value) {
+ fc.AddField(FieldType::UserSystem, field_name, field_value);
+ };
+ add_field("CPU_Model", caps.cpu_string);
+ add_field("CPU_BrandString", caps.brand_string);
+
+ add_field("CPU_Extension_x64_SSE", caps.sse);
+ add_field("CPU_Extension_x64_SSE2", caps.sse2);
+ add_field("CPU_Extension_x64_SSE3", caps.sse3);
+ add_field("CPU_Extension_x64_SSSE3", caps.ssse3);
+ add_field("CPU_Extension_x64_SSE41", caps.sse4_1);
+ add_field("CPU_Extension_x64_SSE42", caps.sse4_2);
+
+ add_field("CPU_Extension_x64_AVX", caps.avx);
+ add_field("CPU_Extension_x64_AVX_VNNI", caps.avx_vnni);
+ add_field("CPU_Extension_x64_AVX2", caps.avx2);
+
+ // Skylake-X/SP level AVX512, for compatibility with the previous telemetry field
+ add_field("CPU_Extension_x64_AVX512",
+ caps.avx512f && caps.avx512cd && caps.avx512vl && caps.avx512dq && caps.avx512bw);
+
+ add_field("CPU_Extension_x64_AVX512F", caps.avx512f);
+ add_field("CPU_Extension_x64_AVX512CD", caps.avx512cd);
+ add_field("CPU_Extension_x64_AVX512VL", caps.avx512vl);
+ add_field("CPU_Extension_x64_AVX512DQ", caps.avx512dq);
+ add_field("CPU_Extension_x64_AVX512BW", caps.avx512bw);
+ add_field("CPU_Extension_x64_AVX512BITALG", caps.avx512bitalg);
+ add_field("CPU_Extension_x64_AVX512VBMI", caps.avx512vbmi);
+
+ add_field("CPU_Extension_x64_AES", caps.aes);
+ add_field("CPU_Extension_x64_BMI1", caps.bmi1);
+ add_field("CPU_Extension_x64_BMI2", caps.bmi2);
+ add_field("CPU_Extension_x64_F16C", caps.f16c);
+ add_field("CPU_Extension_x64_FMA", caps.fma);
+ add_field("CPU_Extension_x64_FMA4", caps.fma4);
+ add_field("CPU_Extension_x64_GFNI", caps.gfni);
+ add_field("CPU_Extension_x64_INVARIANT_TSC", caps.invariant_tsc);
+ add_field("CPU_Extension_x64_LZCNT", caps.lzcnt);
+ add_field("CPU_Extension_x64_MOVBE", caps.movbe);
+ add_field("CPU_Extension_x64_PCLMULQDQ", caps.pclmulqdq);
+ add_field("CPU_Extension_x64_POPCNT", caps.popcnt);
+ add_field("CPU_Extension_x64_SHA", caps.sha);
#else
fc.AddField(FieldType::UserSystem, "CPU_Model", "Other");
#endif
diff --git a/src/common/telemetry.h b/src/common/telemetry.h
index d38aeac99..ba633d5a5 100644
--- a/src/common/telemetry.h
+++ b/src/common/telemetry.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -55,8 +54,8 @@ class Field : public FieldInterface {
public:
YUZU_NON_COPYABLE(Field);
- Field(FieldType type_, std::string name_, T value_)
- : name(std::move(name_)), type(type_), value(std::move(value_)) {}
+ Field(FieldType type_, std::string_view name_, T value_)
+ : name(name_), type(type_), value(std::move(value_)) {}
~Field() override = default;
@@ -123,7 +122,7 @@ public:
* @param value Value for the field to add.
*/
template <typename T>
- void AddField(FieldType type, const char* name, T value) {
+ void AddField(FieldType type, std::string_view name, T value) {
return AddField(std::make_unique<Field<T>>(type, name, std::move(value)));
}
@@ -171,6 +170,9 @@ struct VisitorInterface {
struct NullVisitor final : public VisitorInterface {
YUZU_NON_COPYABLE(NullVisitor);
+ NullVisitor() = default;
+ ~NullVisitor() override = default;
+
void Visit(const Field<bool>& /*field*/) override {}
void Visit(const Field<double>& /*field*/) override {}
void Visit(const Field<float>& /*field*/) override {}
diff --git a/src/common/thread.cpp b/src/common/thread.cpp
index 946a1114d..919e33af9 100644
--- a/src/common/thread.cpp
+++ b/src/common/thread.cpp
@@ -1,6 +1,6 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
@@ -47,6 +47,9 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
case ThreadPriority::VeryHigh:
windows_priority = THREAD_PRIORITY_HIGHEST;
break;
+ case ThreadPriority::Critical:
+ windows_priority = THREAD_PRIORITY_TIME_CRITICAL;
+ break;
default:
windows_priority = THREAD_PRIORITY_NORMAL;
break;
@@ -59,9 +62,10 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
void SetCurrentThreadPriority(ThreadPriority new_priority) {
pthread_t this_thread = pthread_self();
- s32 max_prio = sched_get_priority_max(SCHED_OTHER);
- s32 min_prio = sched_get_priority_min(SCHED_OTHER);
- u32 level = static_cast<u32>(new_priority) + 1;
+ const auto scheduling_type = SCHED_OTHER;
+ s32 max_prio = sched_get_priority_max(scheduling_type);
+ s32 min_prio = sched_get_priority_min(scheduling_type);
+ u32 level = std::max(static_cast<u32>(new_priority) + 1, 4U);
struct sched_param params;
if (max_prio > min_prio) {
@@ -70,7 +74,7 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
}
- pthread_setschedparam(this_thread, SCHED_OTHER, &params);
+ pthread_setschedparam(this_thread, scheduling_type, &params);
}
#endif
diff --git a/src/common/thread.h b/src/common/thread.h
index a8c17c71a..e17a7850f 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -1,6 +1,6 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -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();
@@ -54,6 +54,10 @@ public:
is_set = false;
}
+ [[nodiscard]] bool IsSet() {
+ return is_set;
+ }
+
private:
std::condition_variable condvar;
std::mutex mutex;
@@ -92,6 +96,7 @@ enum class ThreadPriority : u32 {
Normal = 1,
High = 2,
VeryHigh = 3,
+ Critical = 4,
};
void SetCurrentThreadPriority(ThreadPriority new_priority);
diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h
index def9e5d8d..ce48cec92 100644
--- a/src/common/thread_queue_list.h
+++ b/src/common/thread_queue_list.h
@@ -1,6 +1,6 @@
-// Copyright 2014 Citra Emulator Project / PPSSPP Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2012 PPSSPP Project
+// SPDX-FileCopyrightText: 2014 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/thread_worker.h b/src/common/thread_worker.h
index cd0017726..62c60f724 100644
--- a/src/common/thread_worker.h
+++ b/src/common/thread_worker.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h
index 2c8c2b90e..053798e79 100644
--- a/src/common/threadsafe_queue.h
+++ b/src/common/threadsafe_queue.h
@@ -1,6 +1,5 @@
-// Copyright 2010 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2010 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -40,7 +39,7 @@ public:
template <typename Arg>
void Push(Arg&& t) {
// create the element, add it to the queue
- write_ptr->current = std::forward<Arg>(t);
+ write_ptr->current = std::move(t);
// set the next pointer to a new element ptr
// then advance the write pointer
ElementPtr* new_ptr = new ElementPtr();
@@ -52,7 +51,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 +158,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/time_zone.cpp b/src/common/time_zone.cpp
index ce239eb63..126836b01 100644
--- a/src/common/time_zone.cpp
+++ b/src/common/time_zone.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <iomanip>
diff --git a/src/common/time_zone.h b/src/common/time_zone.h
index 9f5939ca5..99cae6ef2 100644
--- a/src/common/time_zone.h
+++ b/src/common/time_zone.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/tiny_mt.h b/src/common/tiny_mt.h
index 19ae5b7d6..5d5ebf158 100644
--- a/src/common/tiny_mt.h
+++ b/src/common/tiny_mt.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/tree.h b/src/common/tree.h
index 18faa4a48..f77859209 100644
--- a/src/common/tree.h
+++ b/src/common/tree.h
@@ -1,32 +1,10 @@
+// SPDX-FileCopyrightText: 2002 Niels Provos <provos@citi.umich.edu>
+// SPDX-License-Identifier: BSD-2-Clause
+
/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */
/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */
/* $FreeBSD$ */
-/*-
- * Copyright 2002 Niels Provos <provos@citi.umich.edu>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
#pragma once
/*
@@ -43,294 +21,265 @@
* The maximum height of a red-black tree is 2lg (n+1).
*/
-#include "common/assert.h"
+namespace Common::freebsd {
+
+enum class RBColor {
+ RB_BLACK = 0,
+ RB_RED = 1,
+};
-namespace Common {
+#pragma pack(push, 4)
template <typename T>
-class RBHead {
+class RBEntry {
public:
- [[nodiscard]] T* Root() {
- return rbh_root;
- }
+ constexpr RBEntry() = default;
- [[nodiscard]] const T* Root() const {
- return rbh_root;
+ [[nodiscard]] constexpr T* Left() {
+ return m_rbe_left;
}
-
- void SetRoot(T* root) {
- rbh_root = root;
+ [[nodiscard]] constexpr const T* Left() const {
+ return m_rbe_left;
}
- [[nodiscard]] bool IsEmpty() const {
- return Root() == nullptr;
+ constexpr void SetLeft(T* e) {
+ m_rbe_left = e;
}
-private:
- T* rbh_root = nullptr;
-};
-
-enum class EntryColor {
- Black,
- Red,
-};
-
-template <typename T>
-class RBEntry {
-public:
- [[nodiscard]] T* Left() {
- return rbe_left;
+ [[nodiscard]] constexpr T* Right() {
+ return m_rbe_right;
}
-
- [[nodiscard]] const T* Left() const {
- return rbe_left;
+ [[nodiscard]] constexpr const T* Right() const {
+ return m_rbe_right;
}
- void SetLeft(T* left) {
- rbe_left = left;
+ constexpr void SetRight(T* e) {
+ m_rbe_right = e;
}
- [[nodiscard]] T* Right() {
- return rbe_right;
+ [[nodiscard]] constexpr T* Parent() {
+ return m_rbe_parent;
}
-
- [[nodiscard]] const T* Right() const {
- return rbe_right;
+ [[nodiscard]] constexpr const T* Parent() const {
+ return m_rbe_parent;
}
- void SetRight(T* right) {
- rbe_right = right;
+ constexpr void SetParent(T* e) {
+ m_rbe_parent = e;
}
- [[nodiscard]] T* Parent() {
- return rbe_parent;
+ [[nodiscard]] constexpr bool IsBlack() const {
+ return m_rbe_color == RBColor::RB_BLACK;
}
-
- [[nodiscard]] const T* Parent() const {
- return rbe_parent;
+ [[nodiscard]] constexpr bool IsRed() const {
+ return m_rbe_color == RBColor::RB_RED;
}
-
- void SetParent(T* parent) {
- rbe_parent = parent;
+ [[nodiscard]] constexpr RBColor Color() const {
+ return m_rbe_color;
}
- [[nodiscard]] bool IsBlack() const {
- return rbe_color == EntryColor::Black;
+ constexpr void SetColor(RBColor c) {
+ m_rbe_color = c;
}
- [[nodiscard]] bool IsRed() const {
- return rbe_color == EntryColor::Red;
- }
+private:
+ T* m_rbe_left{};
+ T* m_rbe_right{};
+ T* m_rbe_parent{};
+ RBColor m_rbe_color{RBColor::RB_BLACK};
+};
+#pragma pack(pop)
- [[nodiscard]] EntryColor Color() const {
- return rbe_color;
- }
+template <typename T>
+struct CheckRBEntry {
+ static constexpr bool value = false;
+};
+template <typename T>
+struct CheckRBEntry<RBEntry<T>> {
+ static constexpr bool value = true;
+};
- void SetColor(EntryColor color) {
- rbe_color = color;
- }
+template <typename T>
+concept IsRBEntry = CheckRBEntry<T>::value;
+
+template <typename T>
+concept HasRBEntry = requires(T& t, const T& ct) {
+ { t.GetRBEntry() } -> std::same_as<RBEntry<T>&>;
+ { ct.GetRBEntry() } -> std::same_as<const RBEntry<T>&>;
+};
+template <typename T>
+requires HasRBEntry<T>
+class RBHead {
private:
- T* rbe_left = nullptr;
- T* rbe_right = nullptr;
- T* rbe_parent = nullptr;
- EntryColor rbe_color{};
+ T* m_rbh_root = nullptr;
+
+public:
+ [[nodiscard]] constexpr T* Root() {
+ return m_rbh_root;
+ }
+ [[nodiscard]] constexpr const T* Root() const {
+ return m_rbh_root;
+ }
+ constexpr void SetRoot(T* root) {
+ m_rbh_root = root;
+ }
+
+ [[nodiscard]] constexpr bool IsEmpty() const {
+ return this->Root() == nullptr;
+ }
};
-template <typename Node>
-[[nodiscard]] RBEntry<Node>& RB_ENTRY(Node* node) {
- return node->GetEntry();
+template <typename T>
+requires HasRBEntry<T>
+[[nodiscard]] constexpr RBEntry<T>& RB_ENTRY(T* t) {
+ return t->GetRBEntry();
}
-
-template <typename Node>
-[[nodiscard]] const RBEntry<Node>& RB_ENTRY(const Node* node) {
- return node->GetEntry();
+template <typename T>
+requires HasRBEntry<T>
+[[nodiscard]] constexpr const RBEntry<T>& RB_ENTRY(const T* t) {
+ return t->GetRBEntry();
}
-template <typename Node>
-[[nodiscard]] Node* RB_PARENT(Node* node) {
- return RB_ENTRY(node).Parent();
+template <typename T>
+requires HasRBEntry<T>
+[[nodiscard]] constexpr T* RB_LEFT(T* t) {
+ return RB_ENTRY(t).Left();
}
-
-template <typename Node>
-[[nodiscard]] const Node* RB_PARENT(const Node* node) {
- return RB_ENTRY(node).Parent();
+template <typename T>
+requires HasRBEntry<T>
+[[nodiscard]] constexpr const T* RB_LEFT(const T* t) {
+ return RB_ENTRY(t).Left();
}
-template <typename Node>
-void RB_SET_PARENT(Node* node, Node* parent) {
- return RB_ENTRY(node).SetParent(parent);
+template <typename T>
+requires HasRBEntry<T>
+[[nodiscard]] constexpr T* RB_RIGHT(T* t) {
+ return RB_ENTRY(t).Right();
}
-
-template <typename Node>
-[[nodiscard]] Node* RB_LEFT(Node* node) {
- return RB_ENTRY(node).Left();
+template <typename T>
+requires HasRBEntry<T>
+[[nodiscard]] constexpr const T* RB_RIGHT(const T* t) {
+ return RB_ENTRY(t).Right();
}
-template <typename Node>
-[[nodiscard]] const Node* RB_LEFT(const Node* node) {
- return RB_ENTRY(node).Left();
+template <typename T>
+requires HasRBEntry<T>
+[[nodiscard]] constexpr T* RB_PARENT(T* t) {
+ return RB_ENTRY(t).Parent();
}
-
-template <typename Node>
-void RB_SET_LEFT(Node* node, Node* left) {
- return RB_ENTRY(node).SetLeft(left);
+template <typename T>
+requires HasRBEntry<T>
+[[nodiscard]] constexpr const T* RB_PARENT(const T* t) {
+ return RB_ENTRY(t).Parent();
}
-template <typename Node>
-[[nodiscard]] Node* RB_RIGHT(Node* node) {
- return RB_ENTRY(node).Right();
+template <typename T>
+requires HasRBEntry<T>
+constexpr void RB_SET_LEFT(T* t, T* e) {
+ RB_ENTRY(t).SetLeft(e);
}
-
-template <typename Node>
-[[nodiscard]] const Node* RB_RIGHT(const Node* node) {
- return RB_ENTRY(node).Right();
+template <typename T>
+requires HasRBEntry<T>
+constexpr void RB_SET_RIGHT(T* t, T* e) {
+ RB_ENTRY(t).SetRight(e);
}
-
-template <typename Node>
-void RB_SET_RIGHT(Node* node, Node* right) {
- return RB_ENTRY(node).SetRight(right);
+template <typename T>
+requires HasRBEntry<T>
+constexpr void RB_SET_PARENT(T* t, T* e) {
+ RB_ENTRY(t).SetParent(e);
}
-template <typename Node>
-[[nodiscard]] bool RB_IS_BLACK(const Node* node) {
- return RB_ENTRY(node).IsBlack();
+template <typename T>
+requires HasRBEntry<T>
+[[nodiscard]] constexpr bool RB_IS_BLACK(const T* t) {
+ return RB_ENTRY(t).IsBlack();
}
-
-template <typename Node>
-[[nodiscard]] bool RB_IS_RED(const Node* node) {
- return RB_ENTRY(node).IsRed();
+template <typename T>
+requires HasRBEntry<T>
+[[nodiscard]] constexpr bool RB_IS_RED(const T* t) {
+ return RB_ENTRY(t).IsRed();
}
-template <typename Node>
-[[nodiscard]] EntryColor RB_COLOR(const Node* node) {
- return RB_ENTRY(node).Color();
+template <typename T>
+requires HasRBEntry<T>
+[[nodiscard]] constexpr RBColor RB_COLOR(const T* t) {
+ return RB_ENTRY(t).Color();
}
-template <typename Node>
-void RB_SET_COLOR(Node* node, EntryColor color) {
- return RB_ENTRY(node).SetColor(color);
+template <typename T>
+requires HasRBEntry<T>
+constexpr void RB_SET_COLOR(T* t, RBColor c) {
+ RB_ENTRY(t).SetColor(c);
}
-template <typename Node>
-void RB_SET(Node* node, Node* parent) {
- auto& entry = RB_ENTRY(node);
- entry.SetParent(parent);
- entry.SetLeft(nullptr);
- entry.SetRight(nullptr);
- entry.SetColor(EntryColor::Red);
+template <typename T>
+requires HasRBEntry<T>
+constexpr void RB_SET(T* elm, T* parent) {
+ auto& rb_entry = RB_ENTRY(elm);
+ rb_entry.SetParent(parent);
+ rb_entry.SetLeft(nullptr);
+ rb_entry.SetRight(nullptr);
+ rb_entry.SetColor(RBColor::RB_RED);
}
-template <typename Node>
-void RB_SET_BLACKRED(Node* black, Node* red) {
- RB_SET_COLOR(black, EntryColor::Black);
- RB_SET_COLOR(red, EntryColor::Red);
+template <typename T>
+requires HasRBEntry<T>
+constexpr void RB_SET_BLACKRED(T* black, T* red) {
+ RB_SET_COLOR(black, RBColor::RB_BLACK);
+ RB_SET_COLOR(red, RBColor::RB_RED);
}
-template <typename Node>
-void RB_ROTATE_LEFT(RBHead<Node>* head, Node* elm, Node*& tmp) {
+template <typename T>
+requires HasRBEntry<T>
+constexpr void RB_ROTATE_LEFT(RBHead<T>& head, T* elm, T*& tmp) {
tmp = RB_RIGHT(elm);
- RB_SET_RIGHT(elm, RB_LEFT(tmp));
- if (RB_RIGHT(elm) != nullptr) {
+ if (RB_SET_RIGHT(elm, RB_LEFT(tmp)); RB_RIGHT(elm) != nullptr) {
RB_SET_PARENT(RB_LEFT(tmp), elm);
}
- RB_SET_PARENT(tmp, RB_PARENT(elm));
- if (RB_PARENT(tmp) != nullptr) {
+ if (RB_SET_PARENT(tmp, RB_PARENT(elm)); RB_PARENT(tmp) != nullptr) {
if (elm == RB_LEFT(RB_PARENT(elm))) {
RB_SET_LEFT(RB_PARENT(elm), tmp);
} else {
RB_SET_RIGHT(RB_PARENT(elm), tmp);
}
} else {
- head->SetRoot(tmp);
+ head.SetRoot(tmp);
}
RB_SET_LEFT(tmp, elm);
RB_SET_PARENT(elm, tmp);
}
-template <typename Node>
-void RB_ROTATE_RIGHT(RBHead<Node>* head, Node* elm, Node*& tmp) {
+template <typename T>
+requires HasRBEntry<T>
+constexpr void RB_ROTATE_RIGHT(RBHead<T>& head, T* elm, T*& tmp) {
tmp = RB_LEFT(elm);
- RB_SET_LEFT(elm, RB_RIGHT(tmp));
- if (RB_LEFT(elm) != nullptr) {
+ if (RB_SET_LEFT(elm, RB_RIGHT(tmp)); RB_LEFT(elm) != nullptr) {
RB_SET_PARENT(RB_RIGHT(tmp), elm);
}
- RB_SET_PARENT(tmp, RB_PARENT(elm));
- if (RB_PARENT(tmp) != nullptr) {
+ if (RB_SET_PARENT(tmp, RB_PARENT(elm)); RB_PARENT(tmp) != nullptr) {
if (elm == RB_LEFT(RB_PARENT(elm))) {
RB_SET_LEFT(RB_PARENT(elm), tmp);
} else {
RB_SET_RIGHT(RB_PARENT(elm), tmp);
}
} else {
- head->SetRoot(tmp);
+ head.SetRoot(tmp);
}
RB_SET_RIGHT(tmp, elm);
RB_SET_PARENT(elm, tmp);
}
-template <typename Node>
-void RB_INSERT_COLOR(RBHead<Node>* head, Node* elm) {
- Node* parent = nullptr;
- Node* tmp = nullptr;
-
- while ((parent = RB_PARENT(elm)) != nullptr && RB_IS_RED(parent)) {
- Node* gparent = RB_PARENT(parent);
- if (parent == RB_LEFT(gparent)) {
- tmp = RB_RIGHT(gparent);
- if (tmp && RB_IS_RED(tmp)) {
- RB_SET_COLOR(tmp, EntryColor::Black);
- RB_SET_BLACKRED(parent, gparent);
- elm = gparent;
- continue;
- }
-
- if (RB_RIGHT(parent) == elm) {
- RB_ROTATE_LEFT(head, parent, tmp);
- tmp = parent;
- parent = elm;
- elm = tmp;
- }
-
- RB_SET_BLACKRED(parent, gparent);
- RB_ROTATE_RIGHT(head, gparent, tmp);
- } else {
- tmp = RB_LEFT(gparent);
- if (tmp && RB_IS_RED(tmp)) {
- RB_SET_COLOR(tmp, EntryColor::Black);
- RB_SET_BLACKRED(parent, gparent);
- elm = gparent;
- continue;
- }
-
- if (RB_LEFT(parent) == elm) {
- RB_ROTATE_RIGHT(head, parent, tmp);
- tmp = parent;
- parent = elm;
- elm = tmp;
- }
-
- RB_SET_BLACKRED(parent, gparent);
- RB_ROTATE_LEFT(head, gparent, tmp);
- }
- }
-
- RB_SET_COLOR(head->Root(), EntryColor::Black);
-}
-
-template <typename Node>
-void RB_REMOVE_COLOR(RBHead<Node>* head, Node* parent, Node* elm) {
- Node* tmp;
- while ((elm == nullptr || RB_IS_BLACK(elm)) && elm != head->Root() && parent != nullptr) {
+template <typename T>
+requires HasRBEntry<T>
+constexpr void RB_REMOVE_COLOR(RBHead<T>& head, T* parent, T* elm) {
+ T* tmp;
+ while ((elm == nullptr || RB_IS_BLACK(elm)) && elm != head.Root()) {
if (RB_LEFT(parent) == elm) {
tmp = RB_RIGHT(parent);
- if (!tmp) {
- ASSERT_MSG(false, "tmp is invalid!");
- break;
- }
if (RB_IS_RED(tmp)) {
RB_SET_BLACKRED(tmp, parent);
RB_ROTATE_LEFT(head, parent, tmp);
@@ -339,29 +288,29 @@ void RB_REMOVE_COLOR(RBHead<Node>* head, Node* parent, Node* elm) {
if ((RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) &&
(RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp)))) {
- RB_SET_COLOR(tmp, EntryColor::Red);
+ RB_SET_COLOR(tmp, RBColor::RB_RED);
elm = parent;
parent = RB_PARENT(elm);
} else {
if (RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp))) {
- Node* oleft;
+ T* oleft;
if ((oleft = RB_LEFT(tmp)) != nullptr) {
- RB_SET_COLOR(oleft, EntryColor::Black);
+ RB_SET_COLOR(oleft, RBColor::RB_BLACK);
}
- RB_SET_COLOR(tmp, EntryColor::Red);
+ RB_SET_COLOR(tmp, RBColor::RB_RED);
RB_ROTATE_RIGHT(head, tmp, oleft);
tmp = RB_RIGHT(parent);
}
RB_SET_COLOR(tmp, RB_COLOR(parent));
- RB_SET_COLOR(parent, EntryColor::Black);
+ RB_SET_COLOR(parent, RBColor::RB_BLACK);
if (RB_RIGHT(tmp)) {
- RB_SET_COLOR(RB_RIGHT(tmp), EntryColor::Black);
+ RB_SET_COLOR(RB_RIGHT(tmp), RBColor::RB_BLACK);
}
RB_ROTATE_LEFT(head, parent, tmp);
- elm = head->Root();
+ elm = head.Root();
break;
}
} else {
@@ -372,68 +321,56 @@ void RB_REMOVE_COLOR(RBHead<Node>* head, Node* parent, Node* elm) {
tmp = RB_LEFT(parent);
}
- if (!tmp) {
- ASSERT_MSG(false, "tmp is invalid!");
- break;
- }
-
if ((RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) &&
(RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp)))) {
- RB_SET_COLOR(tmp, EntryColor::Red);
+ RB_SET_COLOR(tmp, RBColor::RB_RED);
elm = parent;
parent = RB_PARENT(elm);
} else {
if (RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) {
- Node* oright;
+ T* oright;
if ((oright = RB_RIGHT(tmp)) != nullptr) {
- RB_SET_COLOR(oright, EntryColor::Black);
+ RB_SET_COLOR(oright, RBColor::RB_BLACK);
}
- RB_SET_COLOR(tmp, EntryColor::Red);
+ RB_SET_COLOR(tmp, RBColor::RB_RED);
RB_ROTATE_LEFT(head, tmp, oright);
tmp = RB_LEFT(parent);
}
RB_SET_COLOR(tmp, RB_COLOR(parent));
- RB_SET_COLOR(parent, EntryColor::Black);
+ RB_SET_COLOR(parent, RBColor::RB_BLACK);
if (RB_LEFT(tmp)) {
- RB_SET_COLOR(RB_LEFT(tmp), EntryColor::Black);
+ RB_SET_COLOR(RB_LEFT(tmp), RBColor::RB_BLACK);
}
RB_ROTATE_RIGHT(head, parent, tmp);
- elm = head->Root();
+ elm = head.Root();
break;
}
}
}
if (elm) {
- RB_SET_COLOR(elm, EntryColor::Black);
+ RB_SET_COLOR(elm, RBColor::RB_BLACK);
}
}
-template <typename Node>
-Node* RB_REMOVE(RBHead<Node>* head, Node* elm) {
- Node* child = nullptr;
- Node* parent = nullptr;
- Node* old = elm;
- EntryColor color{};
-
- const auto finalize = [&] {
- if (color == EntryColor::Black) {
- RB_REMOVE_COLOR(head, parent, child);
- }
-
- return old;
- };
+template <typename T>
+requires HasRBEntry<T>
+constexpr T* RB_REMOVE(RBHead<T>& head, T* elm) {
+ T* child = nullptr;
+ T* parent = nullptr;
+ T* old = elm;
+ RBColor color = RBColor::RB_BLACK;
if (RB_LEFT(elm) == nullptr) {
child = RB_RIGHT(elm);
} else if (RB_RIGHT(elm) == nullptr) {
child = RB_LEFT(elm);
} else {
- Node* left;
+ T* left;
elm = RB_RIGHT(elm);
while ((left = RB_LEFT(elm)) != nullptr) {
elm = left;
@@ -446,6 +383,7 @@ Node* RB_REMOVE(RBHead<Node>* head, Node* elm) {
if (child) {
RB_SET_PARENT(child, parent);
}
+
if (parent) {
if (RB_LEFT(parent) == elm) {
RB_SET_LEFT(parent, child);
@@ -453,14 +391,14 @@ Node* RB_REMOVE(RBHead<Node>* head, Node* elm) {
RB_SET_RIGHT(parent, child);
}
} else {
- head->SetRoot(child);
+ head.SetRoot(child);
}
if (RB_PARENT(elm) == old) {
parent = elm;
}
- elm->SetEntry(old->GetEntry());
+ elm->SetRBEntry(old->GetRBEntry());
if (RB_PARENT(old)) {
if (RB_LEFT(RB_PARENT(old)) == old) {
@@ -469,17 +407,24 @@ Node* RB_REMOVE(RBHead<Node>* head, Node* elm) {
RB_SET_RIGHT(RB_PARENT(old), elm);
}
} else {
- head->SetRoot(elm);
+ head.SetRoot(elm);
}
+
RB_SET_PARENT(RB_LEFT(old), elm);
+
if (RB_RIGHT(old)) {
RB_SET_PARENT(RB_RIGHT(old), elm);
}
+
if (parent) {
left = parent;
}
- return finalize();
+ if (color == RBColor::RB_BLACK) {
+ RB_REMOVE_COLOR(head, parent, child);
+ }
+
+ return old;
}
parent = RB_PARENT(elm);
@@ -495,17 +440,69 @@ Node* RB_REMOVE(RBHead<Node>* head, Node* elm) {
RB_SET_RIGHT(parent, child);
}
} else {
- head->SetRoot(child);
+ head.SetRoot(child);
+ }
+
+ if (color == RBColor::RB_BLACK) {
+ RB_REMOVE_COLOR(head, parent, child);
+ }
+
+ return old;
+}
+
+template <typename T>
+requires HasRBEntry<T>
+constexpr void RB_INSERT_COLOR(RBHead<T>& head, T* elm) {
+ T *parent = nullptr, *tmp = nullptr;
+ while ((parent = RB_PARENT(elm)) != nullptr && RB_IS_RED(parent)) {
+ T* gparent = RB_PARENT(parent);
+ if (parent == RB_LEFT(gparent)) {
+ tmp = RB_RIGHT(gparent);
+ if (tmp && RB_IS_RED(tmp)) {
+ RB_SET_COLOR(tmp, RBColor::RB_BLACK);
+ RB_SET_BLACKRED(parent, gparent);
+ elm = gparent;
+ continue;
+ }
+
+ if (RB_RIGHT(parent) == elm) {
+ RB_ROTATE_LEFT(head, parent, tmp);
+ tmp = parent;
+ parent = elm;
+ elm = tmp;
+ }
+
+ RB_SET_BLACKRED(parent, gparent);
+ RB_ROTATE_RIGHT(head, gparent, tmp);
+ } else {
+ tmp = RB_LEFT(gparent);
+ if (tmp && RB_IS_RED(tmp)) {
+ RB_SET_COLOR(tmp, RBColor::RB_BLACK);
+ RB_SET_BLACKRED(parent, gparent);
+ elm = gparent;
+ continue;
+ }
+
+ if (RB_LEFT(parent) == elm) {
+ RB_ROTATE_RIGHT(head, parent, tmp);
+ tmp = parent;
+ parent = elm;
+ elm = tmp;
+ }
+
+ RB_SET_BLACKRED(parent, gparent);
+ RB_ROTATE_LEFT(head, gparent, tmp);
+ }
}
- return finalize();
+ RB_SET_COLOR(head.Root(), RBColor::RB_BLACK);
}
-// Inserts a node into the RB tree
-template <typename Node, typename CompareFunction>
-Node* RB_INSERT(RBHead<Node>* head, Node* elm, CompareFunction cmp) {
- Node* parent = nullptr;
- Node* tmp = head->Root();
+template <typename T, typename Compare>
+requires HasRBEntry<T>
+constexpr T* RB_INSERT(RBHead<T>& head, T* elm, Compare cmp) {
+ T* parent = nullptr;
+ T* tmp = head.Root();
int comp = 0;
while (tmp) {
@@ -529,17 +526,17 @@ Node* RB_INSERT(RBHead<Node>* head, Node* elm, CompareFunction cmp) {
RB_SET_RIGHT(parent, elm);
}
} else {
- head->SetRoot(elm);
+ head.SetRoot(elm);
}
RB_INSERT_COLOR(head, elm);
return nullptr;
}
-// Finds the node with the same key as elm
-template <typename Node, typename CompareFunction>
-Node* RB_FIND(RBHead<Node>* head, Node* elm, CompareFunction cmp) {
- Node* tmp = head->Root();
+template <typename T, typename Compare>
+requires HasRBEntry<T>
+constexpr T* RB_FIND(RBHead<T>& head, T* elm, Compare cmp) {
+ T* tmp = head.Root();
while (tmp) {
const int comp = cmp(elm, tmp);
@@ -555,11 +552,11 @@ Node* RB_FIND(RBHead<Node>* head, Node* elm, CompareFunction cmp) {
return nullptr;
}
-// Finds the first node greater than or equal to the search key
-template <typename Node, typename CompareFunction>
-Node* RB_NFIND(RBHead<Node>* head, Node* elm, CompareFunction cmp) {
- Node* tmp = head->Root();
- Node* res = nullptr;
+template <typename T, typename Compare>
+requires HasRBEntry<T>
+constexpr T* RB_NFIND(RBHead<T>& head, T* elm, Compare cmp) {
+ T* tmp = head.Root();
+ T* res = nullptr;
while (tmp) {
const int comp = cmp(elm, tmp);
@@ -576,13 +573,13 @@ Node* RB_NFIND(RBHead<Node>* head, Node* elm, CompareFunction cmp) {
return res;
}
-// Finds the node with the same key as lelm
-template <typename Node, typename CompareFunction>
-Node* RB_FIND_LIGHT(RBHead<Node>* head, const void* lelm, CompareFunction lcmp) {
- Node* tmp = head->Root();
+template <typename T, typename U, typename Compare>
+requires HasRBEntry<T>
+constexpr T* RB_FIND_KEY(RBHead<T>& head, const U& key, Compare cmp) {
+ T* tmp = head.Root();
while (tmp) {
- const int comp = lcmp(lelm, tmp);
+ const int comp = cmp(key, tmp);
if (comp < 0) {
tmp = RB_LEFT(tmp);
} else if (comp > 0) {
@@ -595,14 +592,14 @@ Node* RB_FIND_LIGHT(RBHead<Node>* head, const void* lelm, CompareFunction lcmp)
return nullptr;
}
-// Finds the first node greater than or equal to the search key
-template <typename Node, typename CompareFunction>
-Node* RB_NFIND_LIGHT(RBHead<Node>* head, const void* lelm, CompareFunction lcmp) {
- Node* tmp = head->Root();
- Node* res = nullptr;
+template <typename T, typename U, typename Compare>
+requires HasRBEntry<T>
+constexpr T* RB_NFIND_KEY(RBHead<T>& head, const U& key, Compare cmp) {
+ T* tmp = head.Root();
+ T* res = nullptr;
while (tmp) {
- const int comp = lcmp(lelm, tmp);
+ const int comp = cmp(key, tmp);
if (comp < 0) {
res = tmp;
tmp = RB_LEFT(tmp);
@@ -616,8 +613,43 @@ Node* RB_NFIND_LIGHT(RBHead<Node>* head, const void* lelm, CompareFunction lcmp)
return res;
}
-template <typename Node>
-Node* RB_NEXT(Node* elm) {
+template <typename T, typename Compare>
+requires HasRBEntry<T>
+constexpr T* RB_FIND_EXISTING(RBHead<T>& head, T* elm, Compare cmp) {
+ T* tmp = head.Root();
+
+ while (true) {
+ const int comp = cmp(elm, tmp);
+ if (comp < 0) {
+ tmp = RB_LEFT(tmp);
+ } else if (comp > 0) {
+ tmp = RB_RIGHT(tmp);
+ } else {
+ return tmp;
+ }
+ }
+}
+
+template <typename T, typename U, typename Compare>
+requires HasRBEntry<T>
+constexpr T* RB_FIND_EXISTING_KEY(RBHead<T>& head, const U& key, Compare cmp) {
+ T* tmp = head.Root();
+
+ while (true) {
+ const int comp = cmp(key, tmp);
+ if (comp < 0) {
+ tmp = RB_LEFT(tmp);
+ } else if (comp > 0) {
+ tmp = RB_RIGHT(tmp);
+ } else {
+ return tmp;
+ }
+ }
+}
+
+template <typename T>
+requires HasRBEntry<T>
+constexpr T* RB_NEXT(T* elm) {
if (RB_RIGHT(elm)) {
elm = RB_RIGHT(elm);
while (RB_LEFT(elm)) {
@@ -636,8 +668,9 @@ Node* RB_NEXT(Node* elm) {
return elm;
}
-template <typename Node>
-Node* RB_PREV(Node* elm) {
+template <typename T>
+requires HasRBEntry<T>
+constexpr T* RB_PREV(T* elm) {
if (RB_LEFT(elm)) {
elm = RB_LEFT(elm);
while (RB_RIGHT(elm)) {
@@ -656,30 +689,32 @@ Node* RB_PREV(Node* elm) {
return elm;
}
-template <typename Node>
-Node* RB_MINMAX(RBHead<Node>* head, bool is_min) {
- Node* tmp = head->Root();
- Node* parent = nullptr;
+template <typename T>
+requires HasRBEntry<T>
+constexpr T* RB_MIN(RBHead<T>& head) {
+ T* tmp = head.Root();
+ T* parent = nullptr;
while (tmp) {
parent = tmp;
- if (is_min) {
- tmp = RB_LEFT(tmp);
- } else {
- tmp = RB_RIGHT(tmp);
- }
+ tmp = RB_LEFT(tmp);
}
return parent;
}
-template <typename Node>
-Node* RB_MIN(RBHead<Node>* head) {
- return RB_MINMAX(head, true);
-}
+template <typename T>
+requires HasRBEntry<T>
+constexpr T* RB_MAX(RBHead<T>& head) {
+ T* tmp = head.Root();
+ T* parent = nullptr;
-template <typename Node>
-Node* RB_MAX(RBHead<Node>* head) {
- return RB_MINMAX(head, false);
+ while (tmp) {
+ parent = tmp;
+ tmp = RB_RIGHT(tmp);
+ }
+
+ return parent;
}
-} // namespace Common
+
+} // namespace Common::freebsd
diff --git a/src/common/uint128.h b/src/common/uint128.h
index 4780b2f9d..f450a6db9 100644
--- a/src/common/uint128.h
+++ b/src/common/uint128.h
@@ -1,10 +1,8 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <cstring>
#include <utility>
#ifdef _MSC_VER
@@ -13,7 +11,7 @@
#pragma intrinsic(_umul128)
#pragma intrinsic(_udiv128)
#else
-#include <x86intrin.h>
+#include <cstring>
#endif
#include "common/common_types.h"
diff --git a/src/common/unique_function.h b/src/common/unique_function.h
index ca0559071..c15d88349 100644
--- a/src/common/unique_function.h
+++ b/src/common/unique_function.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/uuid.cpp b/src/common/uuid.cpp
index 2b6a530e3..89e1ed225 100644
--- a/src/common/uuid.cpp
+++ b/src/common/uuid.cpp
@@ -1,6 +1,5 @@
-// Copyright 2022 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <bit>
#include <optional>
diff --git a/src/common/uuid.h b/src/common/uuid.h
index fe31e64e6..7172ca165 100644
--- a/src/common/uuid.h
+++ b/src/common/uuid.h
@@ -1,13 +1,11 @@
-// Copyright 2022 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <functional>
#include <string>
-#include <string_view>
#include "common/common_types.h"
diff --git a/src/common/vector_math.h b/src/common/vector_math.h
index ba7c363c1..e62eeea2e 100644
--- a/src/common/vector_math.h
+++ b/src/common/vector_math.h
@@ -1,32 +1,6 @@
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-// Copyright 2014 Tony Wasserka
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above copyright
-// notice, this list of conditions and the following disclaimer in the
-// documentation and/or other materials provided with the distribution.
-// * Neither the name of the owner nor the names of its contributors may
-// be used to endorse or promote products derived from this software
-// without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// SPDX-FileCopyrightText: 2014 Tony Wasserka
+// SPDX-FileCopyrightText: 2014 Dolphin Emulator Project
+// SPDX-License-Identifier: BSD-3-Clause AND GPL-2.0-or-later
#pragma once
diff --git a/src/common/virtual_buffer.cpp b/src/common/virtual_buffer.cpp
index e3ca29258..dea6de99f 100644
--- a/src/common/virtual_buffer.cpp
+++ b/src/common/virtual_buffer.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef _WIN32
#include <windows.h>
diff --git a/src/common/virtual_buffer.h b/src/common/virtual_buffer.h
index fb1a6f81f..4f6e3e6e5 100644
--- a/src/common/virtual_buffer.h
+++ b/src/common/virtual_buffer.h
@@ -1,10 +1,8 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <type_traits>
#include <utility>
namespace Common {
diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp
index 9acf7551e..ae07f2811 100644
--- a/src/common/wall_clock.cpp
+++ b/src/common/wall_clock.cpp
@@ -1,8 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <cstdint>
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/uint128.h"
#include "common/wall_clock.h"
@@ -70,7 +67,7 @@ std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
const auto& caps = GetCPUCaps();
u64 rtsc_frequency = 0;
if (caps.invariant_tsc) {
- rtsc_frequency = EstimateRDTSCFrequency();
+ rtsc_frequency = caps.tsc_frequency ? caps.tsc_frequency : EstimateRDTSCFrequency();
}
// Fallback to StandardWallClock if the hardware TSC does not have the precision greater than:
diff --git a/src/common/wall_clock.h b/src/common/wall_clock.h
index 874448c27..828a523a8 100644
--- a/src/common/wall_clock.h
+++ b/src/common/wall_clock.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp
index fbeacc7e2..1a27532d4 100644
--- a/src/common/x64/cpu_detect.cpp
+++ b/src/common/x64/cpu_detect.cpp
@@ -1,8 +1,12 @@
-// Copyright 2013 Dolphin Emulator Project / 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2013 Dolphin Emulator Project / 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <array>
#include <cstring>
+#include <iterator>
+#include <string_view>
+#include "common/bit_util.h"
#include "common/common_types.h"
#include "common/x64/cpu_detect.h"
@@ -17,7 +21,7 @@
// clang-format on
#endif
-static inline void __cpuidex(int info[4], int function_id, int subfunction_id) {
+static inline void __cpuidex(int info[4], u32 function_id, u32 subfunction_id) {
#if defined(__DragonFly__) || defined(__FreeBSD__)
// Despite the name, this is just do_cpuid() with ECX as second input.
cpuid_count((u_int)function_id, (u_int)subfunction_id, (u_int*)info);
@@ -30,7 +34,7 @@ static inline void __cpuidex(int info[4], int function_id, int subfunction_id) {
#endif
}
-static inline void __cpuid(int info[4], int function_id) {
+static inline void __cpuid(int info[4], u32 function_id) {
return __cpuidex(info, function_id, 0);
}
@@ -45,6 +49,17 @@ static inline u64 _xgetbv(u32 index) {
namespace Common {
+CPUCaps::Manufacturer CPUCaps::ParseManufacturer(std::string_view brand_string) {
+ if (brand_string == "GenuineIntel") {
+ return Manufacturer::Intel;
+ } else if (brand_string == "AuthenticAMD") {
+ return Manufacturer::AMD;
+ } else if (brand_string == "HygonGenuine") {
+ return Manufacturer::Hygon;
+ }
+ return Manufacturer::Unknown;
+}
+
// Detects the various CPU features
static CPUCaps Detect() {
CPUCaps caps = {};
@@ -53,75 +68,74 @@ static CPUCaps Detect() {
// yuzu at all anyway
int cpu_id[4];
- memset(caps.brand_string, 0, sizeof(caps.brand_string));
- // Detect CPU's CPUID capabilities and grab CPU string
+ // Detect CPU's CPUID capabilities and grab manufacturer string
__cpuid(cpu_id, 0x00000000);
- u32 max_std_fn = cpu_id[0]; // EAX
-
- std::memcpy(&caps.brand_string[0], &cpu_id[1], sizeof(int));
- std::memcpy(&caps.brand_string[4], &cpu_id[3], sizeof(int));
- std::memcpy(&caps.brand_string[8], &cpu_id[2], sizeof(int));
- if (cpu_id[1] == 0x756e6547 && cpu_id[2] == 0x6c65746e && cpu_id[3] == 0x49656e69)
- caps.manufacturer = Manufacturer::Intel;
- else if (cpu_id[1] == 0x68747541 && cpu_id[2] == 0x444d4163 && cpu_id[3] == 0x69746e65)
- caps.manufacturer = Manufacturer::AMD;
- else if (cpu_id[1] == 0x6f677948 && cpu_id[2] == 0x656e6975 && cpu_id[3] == 0x6e65476e)
- caps.manufacturer = Manufacturer::Hygon;
- else
- caps.manufacturer = Manufacturer::Unknown;
+ const u32 max_std_fn = cpu_id[0]; // EAX
- __cpuid(cpu_id, 0x80000000);
+ std::memset(caps.brand_string, 0, std::size(caps.brand_string));
+ std::memcpy(&caps.brand_string[0], &cpu_id[1], sizeof(u32));
+ std::memcpy(&caps.brand_string[4], &cpu_id[3], sizeof(u32));
+ std::memcpy(&caps.brand_string[8], &cpu_id[2], sizeof(u32));
+
+ caps.manufacturer = CPUCaps::ParseManufacturer(caps.brand_string);
+
+ // Set reasonable default cpu string even if brand string not available
+ std::strncpy(caps.cpu_string, caps.brand_string, std::size(caps.brand_string));
- u32 max_ex_fn = cpu_id[0];
+ __cpuid(cpu_id, 0x80000000);
- // Set reasonable default brand string even if brand string not available
- strcpy(caps.cpu_string, caps.brand_string);
+ const u32 max_ex_fn = cpu_id[0];
// Detect family and other miscellaneous features
if (max_std_fn >= 1) {
__cpuid(cpu_id, 0x00000001);
- if ((cpu_id[3] >> 25) & 1)
- caps.sse = true;
- if ((cpu_id[3] >> 26) & 1)
- caps.sse2 = true;
- if ((cpu_id[2]) & 1)
- caps.sse3 = true;
- if ((cpu_id[2] >> 9) & 1)
- caps.ssse3 = true;
- if ((cpu_id[2] >> 19) & 1)
- caps.sse4_1 = true;
- if ((cpu_id[2] >> 20) & 1)
- caps.sse4_2 = true;
- if ((cpu_id[2] >> 25) & 1)
- caps.aes = true;
+ caps.sse = Common::Bit<25>(cpu_id[3]);
+ caps.sse2 = Common::Bit<26>(cpu_id[3]);
+ caps.sse3 = Common::Bit<0>(cpu_id[2]);
+ caps.pclmulqdq = Common::Bit<1>(cpu_id[2]);
+ caps.ssse3 = Common::Bit<9>(cpu_id[2]);
+ caps.sse4_1 = Common::Bit<19>(cpu_id[2]);
+ caps.sse4_2 = Common::Bit<20>(cpu_id[2]);
+ caps.movbe = Common::Bit<22>(cpu_id[2]);
+ caps.popcnt = Common::Bit<23>(cpu_id[2]);
+ caps.aes = Common::Bit<25>(cpu_id[2]);
+ caps.f16c = Common::Bit<29>(cpu_id[2]);
// AVX support requires 3 separate checks:
// - Is the AVX bit set in CPUID?
// - Is the XSAVE bit set in CPUID?
// - XGETBV result has the XCR bit set.
- if (((cpu_id[2] >> 28) & 1) && ((cpu_id[2] >> 27) & 1)) {
+ if (Common::Bit<28>(cpu_id[2]) && Common::Bit<27>(cpu_id[2])) {
if ((_xgetbv(_XCR_XFEATURE_ENABLED_MASK) & 0x6) == 0x6) {
caps.avx = true;
- if ((cpu_id[2] >> 12) & 1)
+ if (Common::Bit<12>(cpu_id[2]))
caps.fma = true;
}
}
if (max_std_fn >= 7) {
__cpuidex(cpu_id, 0x00000007, 0x00000000);
- // Can't enable AVX2 unless the XSAVE/XGETBV checks above passed
- if ((cpu_id[1] >> 5) & 1)
- caps.avx2 = caps.avx;
- if ((cpu_id[1] >> 3) & 1)
- caps.bmi1 = true;
- if ((cpu_id[1] >> 8) & 1)
- caps.bmi2 = true;
- // Checks for AVX512F, AVX512CD, AVX512VL, AVX512DQ, AVX512BW (Intel Skylake-X/SP)
- if ((cpu_id[1] >> 16) & 1 && (cpu_id[1] >> 28) & 1 && (cpu_id[1] >> 31) & 1 &&
- (cpu_id[1] >> 17) & 1 && (cpu_id[1] >> 30) & 1) {
- caps.avx512 = caps.avx2;
+ // Can't enable AVX{2,512} unless the XSAVE/XGETBV checks above passed
+ if (caps.avx) {
+ caps.avx2 = Common::Bit<5>(cpu_id[1]);
+ caps.avx512f = Common::Bit<16>(cpu_id[1]);
+ caps.avx512dq = Common::Bit<17>(cpu_id[1]);
+ caps.avx512cd = Common::Bit<28>(cpu_id[1]);
+ caps.avx512bw = Common::Bit<30>(cpu_id[1]);
+ caps.avx512vl = Common::Bit<31>(cpu_id[1]);
+ caps.avx512vbmi = Common::Bit<1>(cpu_id[2]);
+ caps.avx512bitalg = Common::Bit<12>(cpu_id[2]);
}
+
+ caps.bmi1 = Common::Bit<3>(cpu_id[1]);
+ caps.bmi2 = Common::Bit<8>(cpu_id[1]);
+ caps.sha = Common::Bit<29>(cpu_id[1]);
+
+ caps.gfni = Common::Bit<8>(cpu_id[2]);
+
+ __cpuidex(cpu_id, 0x00000007, 0x00000001);
+ caps.avx_vnni = caps.avx && Common::Bit<4>(cpu_id[0]);
}
}
@@ -138,14 +152,28 @@ static CPUCaps Detect() {
if (max_ex_fn >= 0x80000001) {
// Check for more features
__cpuid(cpu_id, 0x80000001);
- if ((cpu_id[2] >> 16) & 1)
- caps.fma4 = true;
+ caps.lzcnt = Common::Bit<5>(cpu_id[2]);
+ caps.fma4 = Common::Bit<16>(cpu_id[2]);
}
if (max_ex_fn >= 0x80000007) {
__cpuid(cpu_id, 0x80000007);
- if (cpu_id[3] & (1 << 8)) {
- caps.invariant_tsc = true;
+ caps.invariant_tsc = Common::Bit<8>(cpu_id[3]);
+ }
+
+ if (max_std_fn >= 0x15) {
+ __cpuid(cpu_id, 0x15);
+ caps.tsc_crystal_ratio_denominator = cpu_id[0];
+ caps.tsc_crystal_ratio_numerator = cpu_id[1];
+ caps.crystal_frequency = cpu_id[2];
+ // Some CPU models might not return a crystal frequency.
+ // The CPU model can be detected to use the values from turbostat
+ // https://github.com/torvalds/linux/blob/master/tools/power/x86/turbostat/turbostat.c#L5569
+ // but it's easier to just estimate the TSC tick rate for these cases.
+ if (caps.tsc_crystal_ratio_denominator) {
+ caps.tsc_frequency = static_cast<u64>(caps.crystal_frequency) *
+ caps.tsc_crystal_ratio_numerator /
+ caps.tsc_crystal_ratio_denominator;
}
}
diff --git a/src/common/x64/cpu_detect.h b/src/common/x64/cpu_detect.h
index e3b63302e..6830f3795 100644
--- a/src/common/x64/cpu_detect.h
+++ b/src/common/x64/cpu_detect.h
@@ -1,42 +1,71 @@
-// Copyright 2013 Dolphin Emulator Project / 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2013 Dolphin Emulator Project / 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-namespace Common {
+#include <string_view>
+#include "common/common_types.h"
-enum class Manufacturer : u32 {
- Intel = 0,
- AMD = 1,
- Hygon = 2,
- Unknown = 3,
-};
+namespace Common {
/// x86/x64 CPU capabilities that may be detected by this module
struct CPUCaps {
+
+ enum class Manufacturer : u8 {
+ Unknown = 0,
+ Intel = 1,
+ AMD = 2,
+ Hygon = 3,
+ };
+
+ static Manufacturer ParseManufacturer(std::string_view brand_string);
+
Manufacturer manufacturer;
- char cpu_string[0x21];
- char brand_string[0x41];
- bool sse;
- bool sse2;
- bool sse3;
- bool ssse3;
- bool sse4_1;
- bool sse4_2;
- bool lzcnt;
- bool avx;
- bool avx2;
- bool avx512;
- bool bmi1;
- bool bmi2;
- bool fma;
- bool fma4;
- bool aes;
- bool invariant_tsc;
+ char brand_string[13];
+
+ char cpu_string[48];
+
u32 base_frequency;
u32 max_frequency;
u32 bus_frequency;
+
+ u32 tsc_crystal_ratio_denominator;
+ u32 tsc_crystal_ratio_numerator;
+ u32 crystal_frequency;
+ u64 tsc_frequency; // Derived from the above three values
+
+ bool sse : 1;
+ bool sse2 : 1;
+ bool sse3 : 1;
+ bool ssse3 : 1;
+ bool sse4_1 : 1;
+ bool sse4_2 : 1;
+
+ bool avx : 1;
+ bool avx_vnni : 1;
+ bool avx2 : 1;
+ bool avx512f : 1;
+ bool avx512dq : 1;
+ bool avx512cd : 1;
+ bool avx512bw : 1;
+ bool avx512vl : 1;
+ bool avx512vbmi : 1;
+ bool avx512bitalg : 1;
+
+ bool aes : 1;
+ bool bmi1 : 1;
+ bool bmi2 : 1;
+ bool f16c : 1;
+ bool fma : 1;
+ bool fma4 : 1;
+ bool gfni : 1;
+ bool invariant_tsc : 1;
+ bool lzcnt : 1;
+ bool movbe : 1;
+ bool pclmulqdq : 1;
+ bool popcnt : 1;
+ bool sha : 1;
};
/**
diff --git a/src/common/x64/native_clock.cpp b/src/common/x64/native_clock.cpp
index 91b842829..8b08332ab 100644
--- a/src/common/x64/native_clock.cpp
+++ b/src/common/x64/native_clock.cpp
@@ -1,36 +1,57 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <chrono>
-#include <limits>
-#include <mutex>
#include <thread>
#include "common/atomic_ops.h"
#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());
@@ -44,8 +65,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);
@@ -57,10 +77,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
@@ -68,22 +88,21 @@ 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));
- /// The clock cannot be more precise than the guest timer, remove the lower bits
- return new_time_point.inner.accumulated_ticks & inaccuracy_mask;
+ current_time_point.pack, current_time_point.pack));
+ return new_time_point.inner.accumulated_ticks;
}
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/common/x64/native_clock.h b/src/common/x64/native_clock.h
index 7cbd400d2..38ae7a462 100644
--- a/src/common/x64/native_clock.h
+++ b/src/common/x64/native_clock.h
@@ -1,11 +1,8 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <optional>
-
#include "common/wall_clock.h"
namespace Common {
@@ -40,12 +37,8 @@ private:
} inner;
};
- /// value used to reduce the native clocks accuracy as some apss rely on
- /// undefined behavior where the level of accuracy in the clock shouldn't
- /// be higher.
- static constexpr u64 inaccuracy_mask = ~(UINT64_C(0x400) - 1);
-
TimePoint time_point;
+
// factors
u64 clock_rtsc_factor{};
u64 cpu_rtsc_factor{};
diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h
index 87b3d63a4..67e6e63c8 100644
--- a/src/common/x64/xbyak_abi.h
+++ b/src/common/x64/xbyak_abi.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h
index 44d2558f1..250e5cddb 100644
--- a/src/common/x64/xbyak_util.h
+++ b/src/common/x64/xbyak_util.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/common/zstd_compression.cpp b/src/common/zstd_compression.cpp
index 695b96a43..b71a41b78 100644
--- a/src/common/zstd_compression.cpp
+++ b/src/common/zstd_compression.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <zstd.h>
diff --git a/src/common/zstd_compression.h b/src/common/zstd_compression.h
index bbce14f4e..a5ab2d05b 100644
--- a/src/common/zstd_compression.h
+++ b/src/common/zstd_compression.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 6e8d11919..95302c419 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -1,18 +1,15 @@
+# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
add_library(core STATIC
arm/arm_interface.h
arm/arm_interface.cpp
- arm/cpu_interrupt_handler.cpp
- arm/cpu_interrupt_handler.h
- arm/dynarmic/arm_dynarmic_32.cpp
- arm/dynarmic/arm_dynarmic_32.h
- arm/dynarmic/arm_dynarmic_64.cpp
- arm/dynarmic/arm_dynarmic_64.h
- arm/dynarmic/arm_dynarmic_cp15.cpp
- arm/dynarmic/arm_dynarmic_cp15.h
arm/dynarmic/arm_exclusive_monitor.cpp
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
@@ -34,6 +31,13 @@ add_library(core STATIC
crypto/ctr_encryption_layer.h
crypto/xts_encryption_layer.cpp
crypto/xts_encryption_layer.h
+ debugger/debugger_interface.h
+ debugger/debugger.cpp
+ debugger/debugger.h
+ debugger/gdbstub_arch.cpp
+ debugger/gdbstub_arch.h
+ debugger/gdbstub.cpp
+ debugger/gdbstub.h
device_memory.cpp
device_memory.h
file_sys/bis_factory.cpp
@@ -122,6 +126,8 @@ add_library(core STATIC
frontend/applets/error.h
frontend/applets/general_frontend.cpp
frontend/applets/general_frontend.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
@@ -132,8 +138,6 @@ add_library(core STATIC
frontend/emu_window.h
frontend/framebuffer_layout.cpp
frontend/framebuffer_layout.h
- hardware_interrupt_manager.cpp
- hardware_interrupt_manager.h
hid/emulated_console.cpp
hid/emulated_console.h
hid/emulated_controller.cpp
@@ -147,11 +151,13 @@ add_library(core STATIC
hid/input_converter.h
hid/input_interpreter.cpp
hid/input_interpreter.h
+ hid/irs_types.h
hid/motion_input.cpp
hid/motion_input.h
hle/api_version.h
hle/ipc.h
hle/ipc_helpers.h
+ hle/kernel/board/nintendo/nx/k_memory_layout.h
hle/kernel/board/nintendo/nx/k_system_control.cpp
hle/kernel/board/nintendo/nx/k_system_control.h
hle/kernel/board/nintendo/nx/secure_monitor.h
@@ -164,6 +170,7 @@ add_library(core STATIC
hle/kernel/hle_ipc.h
hle/kernel/init/init_slab_setup.cpp
hle/kernel/init/init_slab_setup.h
+ hle/kernel/initial_process.h
hle/kernel/k_address_arbiter.cpp
hle/kernel/k_address_arbiter.h
hle/kernel/k_address_space_info.cpp
@@ -205,9 +212,11 @@ add_library(core STATIC
hle/kernel/k_memory_region.h
hle/kernel/k_memory_region_type.h
hle/kernel/k_page_bitmap.h
+ hle/kernel/k_page_buffer.cpp
+ hle/kernel/k_page_buffer.h
hle/kernel/k_page_heap.cpp
hle/kernel/k_page_heap.h
- hle/kernel/k_page_linked_list.h
+ hle/kernel/k_page_group.h
hle/kernel/k_page_table.cpp
hle/kernel/k_page_table.h
hle/kernel/k_port.cpp
@@ -242,6 +251,8 @@ add_library(core STATIC
hle/kernel/k_system_control.h
hle/kernel/k_thread.cpp
hle/kernel/k_thread.h
+ hle/kernel/k_thread_local_page.cpp
+ hle/kernel/k_thread_local_page.h
hle/kernel/k_thread_queue.cpp
hle/kernel/k_thread_queue.h
hle/kernel/k_trace.h
@@ -298,6 +309,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_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
@@ -421,8 +435,11 @@ 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/irs_ring_lifo.h
hle/service/hid/ring_lifo.h
hle/service/hid/xcd.cpp
hle/service/hid/xcd.h
@@ -441,17 +458,48 @@ add_library(core STATIC
hle/service/hid/controllers/mouse.h
hle/service/hid/controllers/npad.cpp
hle/service/hid/controllers/npad.h
+ hle/service/hid/controllers/palma.cpp
+ hle/service/hid/controllers/palma.h
hle/service/hid/controllers/stubbed.cpp
hle/service/hid/controllers/stubbed.h
hle/service/hid/controllers/touchscreen.cpp
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/hid/irsensor/clustering_processor.cpp
+ hle/service/hid/irsensor/clustering_processor.h
+ hle/service/hid/irsensor/image_transfer_processor.cpp
+ hle/service/hid/irsensor/image_transfer_processor.h
+ hle/service/hid/irsensor/ir_led_processor.cpp
+ hle/service/hid/irsensor/ir_led_processor.h
+ hle/service/hid/irsensor/moment_processor.cpp
+ hle/service/hid/irsensor/moment_processor.h
+ hle/service/hid/irsensor/pointing_processor.cpp
+ hle/service/hid/irsensor/pointing_processor.h
+ hle/service/hid/irsensor/processor_base.cpp
+ hle/service/hid/irsensor/processor_base.h
+ hle/service/hid/irsensor/tera_plugin_processor.cpp
+ hle/service/hid/irsensor/tera_plugin_processor.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
+ hle/service/ldn/lan_discovery.cpp
+ hle/service/ldn/lan_discovery.h
+ hle/service/ldn/ldn_results.h
hle/service/ldn/ldn.cpp
hle/service/ldn/ldn.h
+ hle/service/ldn/ldn_types.h
hle/service/ldr/ldr.cpp
hle/service/ldr/ldr.h
hle/service/lm/lm.cpp
@@ -467,12 +515,20 @@ add_library(core STATIC
hle/service/mii/types.h
hle/service/mm/mm_u.cpp
hle/service/mm/mm_u.h
+ hle/service/mnpp/mnpp_app.cpp
+ hle/service/mnpp/mnpp_app.h
hle/service/ncm/ncm.cpp
hle/service/ncm/ncm.h
hle/service/nfc/nfc.cpp
hle/service/nfc/nfc.h
+ hle/service/nfp/amiibo_crypto.cpp
+ hle/service/nfp/amiibo_crypto.h
hle/service/nfp/nfp.cpp
hle/service/nfp/nfp.h
+ hle/service/nfp/nfp_device.cpp
+ hle/service/nfp/nfp_device.h
+ hle/service/nfp/nfp_result.h
+ hle/service/nfp/nfp_types.h
hle/service/nfp/nfp_user.cpp
hle/service/nfp/nfp_user.h
hle/service/ngct/ngct.cpp
@@ -484,14 +540,20 @@ add_library(core STATIC
hle/service/npns/npns.cpp
hle/service/npns/npns.h
hle/service/ns/errors.h
+ hle/service/ns/iplatform_service_manager.cpp
+ hle/service/ns/iplatform_service_manager.h
hle/service/ns/language.cpp
hle/service/ns/language.h
hle/service/ns/ns.cpp
hle/service/ns/ns.h
hle/service/ns/pdm_qry.cpp
hle/service/ns/pdm_qry.h
- hle/service/ns/pl_u.cpp
- hle/service/ns/pl_u.h
+ hle/service/nvdrv/core/container.cpp
+ hle/service/nvdrv/core/container.h
+ hle/service/nvdrv/core/nvmap.cpp
+ hle/service/nvdrv/core/nvmap.h
+ hle/service/nvdrv/core/syncpoint_manager.cpp
+ hle/service/nvdrv/core/syncpoint_manager.h
hle/service/nvdrv/devices/nvdevice.h
hle/service/nvdrv/devices/nvdisp_disp0.cpp
hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -520,12 +582,35 @@ add_library(core STATIC
hle/service/nvdrv/nvdrv_interface.h
hle/service/nvdrv/nvmemp.cpp
hle/service/nvdrv/nvmemp.h
- hle/service/nvdrv/syncpoint_manager.cpp
- hle/service/nvdrv/syncpoint_manager.h
- hle/service/nvflinger/buffer_queue.cpp
- hle/service/nvflinger/buffer_queue.h
+ hle/service/nvflinger/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
@@ -544,6 +629,10 @@ add_library(core STATIC
hle/service/psc/psc.h
hle/service/ptm/psm.cpp
hle/service/ptm/psm.h
+ hle/service/ptm/ptm.cpp
+ hle/service/ptm/ptm.h
+ hle/service/ptm/ts.cpp
+ hle/service/ptm/ts.h
hle/service/kernel_helpers.cpp
hle/service/kernel_helpers.h
hle/service/service.cpp
@@ -634,10 +723,15 @@ add_library(core STATIC
hle/service/vi/vi_u.h
hle/service/wlan/wlan.cpp
hle/service/wlan/wlan.h
+ internal_network/network.cpp
+ internal_network/network.h
+ internal_network/network_interface.cpp
+ internal_network/network_interface.h
+ internal_network/sockets.h
+ internal_network/socket_proxy.cpp
+ internal_network/socket_proxy.h
loader/deconstructed_rom_directory.cpp
loader/deconstructed_rom_directory.h
- loader/elf.cpp
- loader/elf.h
loader/kip.cpp
loader/kip.h
loader/loader.cpp
@@ -661,11 +755,6 @@ add_library(core STATIC
memory/dmnt_cheat_vm.h
memory.cpp
memory.h
- network/network.cpp
- network/network.h
- network/network_interface.cpp
- network/network_interface.h
- network/sockets.h
perf_stats.cpp
perf_stats.h
reporter.cpp
@@ -682,16 +771,11 @@ if (MSVC)
/we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data
/we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch
/we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
- /we4456 # Declaration of 'identifier' hides previous local declaration
- /we4457 # Declaration of 'identifier' hides function parameter
- /we4458 # Declaration of 'identifier' hides class member
- /we4459 # Declaration of 'identifier' hides global declaration
)
else()
target_compile_options(core PRIVATE
-Werror=conversion
-Werror=ignored-qualifiers
- -Werror=shadow
$<$<CXX_COMPILER_ID:GNU>:-Werror=class-memaccess>
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
@@ -705,8 +789,11 @@ endif()
create_target_directory_groups(core)
-target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
-target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus)
+target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core)
+target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus)
+if (MINGW)
+ target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
+endif()
if (ENABLE_WEB_SERVICE)
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index 0951e1976..953d96439 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -1,6 +1,9 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifndef _MSC_VER
+#include <cxxabi.h>
+#endif
#include <map>
#include <optional>
@@ -8,170 +11,43 @@
#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/debugger/debugger.h"
+#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/svc.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 {};
- }
-
- 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;
- }
+#include "core/arm/dynarmic/arm_dynarmic_32.h"
+#include "core/arm/dynarmic/arm_dynarmic_64.h"
- 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;
- }
+ Core::System& system, const ARM_Interface::ThreadContext32& ctx) {
+ return ARM_Dynarmic_32::GetBacktraceFromContext(system, ctx);
+}
- 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) {
@@ -188,91 +64,137 @@ std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktraceFromContex
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()) {
+#ifdef _MSC_VER
// TODO(DarkLordZach): Add demangling of symbol names.
entry.name = *symbol;
+#else
+ int status{-1};
+ char* demangled{abi::__cxa_demangle(symbol->c_str(), nullptr, nullptr, &status)};
+ if (status == 0 && demangled != nullptr) {
+ entry.name = demangled;
+ std::free(demangled);
+ } else {
+ entry.name = *symbol;
+ }
+#endif
}
}
}
+}
+
+void ARM_Interface::LogBacktrace() const {
+ 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",
+ "Offset", "Symbol");
+ LOG_ERROR(Core_ARM, "");
- return out;
+ const auto backtrace = GetBacktrace();
+ for (const auto& entry : backtrace) {
+ LOG_ERROR(Core_ARM, "{:20}{:016X} {:016X} {:016X} {}", entry.module, entry.address,
+ entry.original_address, entry.offset, entry.name);
+ }
}
-std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktrace() const {
- std::vector<BacktraceEntry> out;
- auto& memory = system.Memory();
+void ARM_Interface::Run() {
+ using Kernel::StepState;
+ using Kernel::SuspendType;
- auto fp = GetReg(29);
- auto lr = GetReg(30);
while (true) {
- out.push_back({"", 0, lr, 0, ""});
- if (!fp) {
+ Kernel::KThread* current_thread{Kernel::GetCurrentThreadPointer(system.Kernel())};
+ Dynarmic::HaltReason hr{};
+
+ // Notify the debugger and go to sleep if a step was performed
+ // and this thread has been scheduled again.
+ if (current_thread->GetStepState() == StepState::StepPerformed) {
+ system.GetDebugger().NotifyThreadStopped(current_thread);
+ current_thread->RequestSuspend(SuspendType::Debug);
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));
- }
+ // Otherwise, run the thread.
+ system.EnterDynarmicProfile();
+ if (current_thread->GetStepState() == StepState::StepPending) {
+ hr = StepJit();
- 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;
+ if (Has(hr, step_thread)) {
+ current_thread->SetStepState(StepState::StepPerformed);
}
+ } else {
+ hr = RunJit();
+ }
+ system.ExitDynarmicProfile();
+
+ // Notify the debugger and go to sleep if a breakpoint was hit,
+ // or if the thread is unable to continue for any reason.
+ if (Has(hr, breakpoint) || Has(hr, no_execute)) {
+ RewindBreakpointInstruction();
+ if (system.DebuggerEnabled()) {
+ system.GetDebugger().NotifyThreadStopped(current_thread);
+ }
+ current_thread->RequestSuspend(Kernel::SuspendType::Debug);
+ 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;
+ // Notify the debugger and go to sleep if a watchpoint was hit.
+ if (Has(hr, watchpoint)) {
+ if (system.DebuggerEnabled()) {
+ system.GetDebugger().NotifyThreadWatchpoint(current_thread, *HaltedWatchpoint());
}
+ current_thread->RequestSuspend(SuspendType::Debug);
+ break;
+ }
+
+ // Handle syscalls and scheduling (this may change the current thread/core)
+ if (Has(hr, svc_call)) {
+ Kernel::Svc::Call(system, GetSvcNumber());
+ break;
+ }
+ if (Has(hr, break_loop) || !uses_wall_clock) {
+ break;
}
}
+}
- return out;
+void ARM_Interface::LoadWatchpointArray(const WatchpointArray& wp) {
+ watchpoints = &wp;
}
-void ARM_Interface::LogBacktrace() const {
- const VAddr sp = GetReg(13);
- 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",
- "Offset", "Symbol");
- LOG_ERROR(Core_ARM, "");
+const Kernel::DebugWatchpoint* ARM_Interface::MatchingWatchpoint(
+ VAddr addr, u64 size, Kernel::DebugWatchpointType access_type) const {
+ if (!watchpoints) {
+ return nullptr;
+ }
- const auto backtrace = GetBacktrace();
- for (const auto& entry : backtrace) {
- LOG_ERROR(Core_ARM, "{:20}{:016X} {:016X} {:016X} {}", entry.module, entry.address,
- entry.original_address, entry.offset, entry.name);
+ const VAddr start_address{addr};
+ const VAddr end_address{addr + size};
+
+ for (size_t i = 0; i < Core::Hardware::NUM_WATCHPOINTS; i++) {
+ const auto& watch{(*watchpoints)[i]};
+
+ if (end_address <= watch.start_address) {
+ continue;
+ }
+ if (start_address >= watch.end_address) {
+ continue;
+ }
+ if ((access_type & watch.type) == Kernel::DebugWatchpointType::None) {
+ continue;
+ }
+
+ return &watch;
}
+
+ return nullptr;
}
} // namespace Core
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index c60322442..7d62d030e 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -1,11 +1,14 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
+#include <span>
#include <vector>
+
+#include <dynarmic/interface/halt_reason.h>
+
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/hardware_properties.h"
@@ -16,13 +19,15 @@ struct PageTable;
namespace Kernel {
enum class VMAPermission : u8;
-}
+enum class DebugWatchpointType : u8;
+struct DebugWatchpoint;
+} // namespace Kernel
namespace Core {
class System;
class CPUInterruptHandler;
-using CPUInterrupts = std::array<CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>;
+using WatchpointArray = std::array<Kernel::DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>;
/// Generic ARMv8 CPU interface
class ARM_Interface {
@@ -30,10 +35,8 @@ public:
YUZU_NON_COPYABLE(ARM_Interface);
YUZU_NON_MOVEABLE(ARM_Interface);
- explicit ARM_Interface(System& system_, CPUInterrupts& interrupt_handlers_,
- bool uses_wall_clock_)
- : system{system_}, interrupt_handlers{interrupt_handlers_}, uses_wall_clock{
- uses_wall_clock_} {}
+ explicit ARM_Interface(System& system_, bool uses_wall_clock_)
+ : system{system_}, uses_wall_clock{uses_wall_clock_} {}
virtual ~ARM_Interface() = default;
struct ThreadContext32 {
@@ -64,10 +67,7 @@ public:
static_assert(sizeof(ThreadContext64) == 0x320);
/// Runs the CPU until an event happens
- virtual void Run() = 0;
-
- /// Step CPU by one instruction
- virtual void Step() = 0;
+ void Run();
/// Clear all instruction cache
virtual void ClearInstructionCache() = 0;
@@ -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
@@ -164,12 +170,16 @@ public:
virtual void SaveContext(ThreadContext64& ctx) = 0;
virtual void LoadContext(const ThreadContext32& ctx) = 0;
virtual void LoadContext(const ThreadContext64& ctx) = 0;
+ void LoadWatchpointArray(const WatchpointArray& wp);
/// Clears the exclusive monitor's state.
virtual void ClearExclusiveState() = 0;
- /// 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;
+
+ /// Clear a previous interrupt.
+ virtual void ClearInterrupt() = 0;
struct BacktraceEntry {
std::string module;
@@ -180,23 +190,36 @@ 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;
+ static constexpr Dynarmic::HaltReason step_thread = Dynarmic::HaltReason::Step;
+ static constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
+ static constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
+ static constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
+ static constexpr Dynarmic::HaltReason watchpoint = Dynarmic::HaltReason::MemoryAbort;
+ static constexpr Dynarmic::HaltReason no_execute = Dynarmic::HaltReason::UserDefined6;
+
protected:
/// System context that this ARM interface is running under.
System& system;
- CPUInterrupts& interrupt_handlers;
+ const WatchpointArray* watchpoints;
bool uses_wall_clock;
+
+ static void SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out);
+ const Kernel::DebugWatchpoint* MatchingWatchpoint(
+ VAddr addr, u64 size, Kernel::DebugWatchpointType access_type) const;
+
+ virtual Dynarmic::HaltReason RunJit() = 0;
+ virtual Dynarmic::HaltReason StepJit() = 0;
+ virtual u32 GetSvcNumber() const = 0;
+ virtual const Kernel::DebugWatchpoint* HaltedWatchpoint() const = 0;
+ virtual void RewindBreakpointInstruction() = 0;
};
} // namespace Core
diff --git a/src/core/arm/cpu_interrupt_handler.cpp b/src/core/arm/cpu_interrupt_handler.cpp
deleted file mode 100644
index 9c8898700..000000000
--- a/src/core/arm/cpu_interrupt_handler.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/thread.h"
-#include "core/arm/cpu_interrupt_handler.h"
-
-namespace Core {
-
-CPUInterruptHandler::CPUInterruptHandler() : interrupt_event{std::make_unique<Common::Event>()} {}
-
-CPUInterruptHandler::~CPUInterruptHandler() = default;
-
-void CPUInterruptHandler::SetInterrupt(bool is_interrupted_) {
- if (is_interrupted_) {
- interrupt_event->Set();
- }
- is_interrupted = is_interrupted_;
-}
-
-void CPUInterruptHandler::AwaitInterrupt() {
- interrupt_event->Wait();
-}
-
-} // namespace Core
diff --git a/src/core/arm/cpu_interrupt_handler.h b/src/core/arm/cpu_interrupt_handler.h
deleted file mode 100644
index c20c280f1..000000000
--- a/src/core/arm/cpu_interrupt_handler.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <atomic>
-#include <memory>
-
-namespace Common {
-class Event;
-}
-
-namespace Core {
-
-class CPUInterruptHandler {
-public:
- CPUInterruptHandler();
- ~CPUInterruptHandler();
-
- CPUInterruptHandler(const CPUInterruptHandler&) = delete;
- CPUInterruptHandler& operator=(const CPUInterruptHandler&) = delete;
-
- CPUInterruptHandler(CPUInterruptHandler&&) = delete;
- CPUInterruptHandler& operator=(CPUInterruptHandler&&) = delete;
-
- bool IsInterrupted() const {
- return is_interrupted;
- }
-
- void SetInterrupt(bool is_interrupted);
-
- void AwaitInterrupt();
-
-private:
- std::unique_ptr<Common::Event> interrupt_event;
- std::atomic_bool is_interrupted{false};
-};
-
-} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index b0d89c539..d1e70f19d 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cinttypes>
#include <memory>
@@ -12,12 +11,13 @@
#include "common/logging/log.h"
#include "common/page_table.h"
#include "common/settings.h"
-#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/debugger/debugger.h"
+#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/svc.h"
#include "core/memory.h"
@@ -28,69 +28,106 @@ using namespace Common::Literals;
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
public:
explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_)
- : parent{parent_}, memory(parent.system.Memory()) {}
+ : parent{parent_},
+ memory(parent.system.Memory()), debugger_enabled{parent.system.DebuggerEnabled()} {}
u8 MemoryRead8(u32 vaddr) override {
+ CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read);
return memory.Read8(vaddr);
}
u16 MemoryRead16(u32 vaddr) override {
+ CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read);
return memory.Read16(vaddr);
}
u32 MemoryRead32(u32 vaddr) override {
+ CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read);
return memory.Read32(vaddr);
}
u64 MemoryRead64(u32 vaddr) override {
+ CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read);
return memory.Read64(vaddr);
}
+ std::optional<u32> MemoryReadCode(u32 vaddr) override {
+ if (!memory.IsValidVirtualAddressRange(vaddr, sizeof(u32))) {
+ return std::nullopt;
+ }
+ return memory.Read32(vaddr);
+ }
void MemoryWrite8(u32 vaddr, u8 value) override {
- memory.Write8(vaddr, value);
+ if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) {
+ memory.Write8(vaddr, value);
+ }
}
void MemoryWrite16(u32 vaddr, u16 value) override {
- memory.Write16(vaddr, value);
+ if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) {
+ memory.Write16(vaddr, value);
+ }
}
void MemoryWrite32(u32 vaddr, u32 value) override {
- memory.Write32(vaddr, value);
+ if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) {
+ memory.Write32(vaddr, value);
+ }
}
void MemoryWrite64(u32 vaddr, u64 value) override {
- memory.Write64(vaddr, value);
+ if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) {
+ memory.Write64(vaddr, value);
+ }
}
bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override {
- return memory.WriteExclusive8(vaddr, value, expected);
+ return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) &&
+ memory.WriteExclusive8(vaddr, value, expected);
}
bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override {
- return memory.WriteExclusive16(vaddr, value, expected);
+ return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) &&
+ memory.WriteExclusive16(vaddr, value, expected);
}
bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override {
- return memory.WriteExclusive32(vaddr, value, expected);
+ return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) &&
+ memory.WriteExclusive32(vaddr, value, expected);
}
bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override {
- return memory.WriteExclusive64(vaddr, value, expected);
+ return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) &&
+ memory.WriteExclusive64(vaddr, value, expected);
}
void InterpreterFallback(u32 pc, std::size_t num_instructions) override {
- UNIMPLEMENTED_MSG("This should never happen, pc = {:08X}, code = {:08X}", pc,
- MemoryReadCode(pc));
+ parent.LogBacktrace();
+ LOG_ERROR(Core_ARM,
+ "Unimplemented instruction @ 0x{:X} for {} instructions (instr = {:08X})", pc,
+ num_instructions, memory.Read32(pc));
}
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
- LOG_CRITICAL(Core_ARM,
- "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})",
- exception, pc, MemoryReadCode(pc), parent.IsInThumbMode());
- UNIMPLEMENTED();
+ switch (exception) {
+ case Dynarmic::A32::Exception::NoExecuteFault:
+ LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#08x}", pc);
+ ReturnException(pc, ARM_Interface::no_execute);
+ return;
+ default:
+ if (debugger_enabled) {
+ ReturnException(pc, ARM_Interface::breakpoint);
+ return;
+ }
+
+ parent.LogBacktrace();
+ LOG_CRITICAL(Core_ARM,
+ "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})",
+ exception, pc, memory.Read32(pc), parent.IsInThumbMode());
+ }
}
void CallSVC(u32 swi) override {
- parent.svc_called = true;
parent.svc_swi = swi;
- parent.jit->HaltExecution();
+ parent.jit.load()->HaltExecution(ARM_Interface::svc_call);
}
void AddTicks(u64 ticks) override {
if (parent.uses_wall_clock) {
return;
}
+
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should
@@ -107,18 +144,45 @@ public:
u64 GetTicksRemaining() override {
if (parent.uses_wall_clock) {
- if (!parent.interrupt_handlers[parent.core_index].IsInterrupted()) {
+ if (!IsInterrupted()) {
return minimum_run_cycles;
}
return 0U;
}
+
return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
}
+ bool CheckMemoryAccess(VAddr addr, u64 size, Kernel::DebugWatchpointType type) {
+ if (!debugger_enabled) {
+ return true;
+ }
+
+ const auto match{parent.MatchingWatchpoint(addr, size, type)};
+ if (match) {
+ parent.halted_watchpoint = match;
+ parent.jit.load()->HaltExecution(ARM_Interface::watchpoint);
+ return false;
+ }
+
+ return true;
+ }
+
+ void ReturnException(u32 pc, Dynarmic::HaltReason hr) {
+ parent.SaveContext(parent.breakpoint_context);
+ parent.breakpoint_context.cpu_registers[15] = pc;
+ parent.jit.load()->HaltExecution(hr);
+ }
+
+ bool IsInterrupted() {
+ return parent.system.Kernel().PhysicalCore(parent.core_index).IsInterrupted();
+ }
+
ARM_Dynarmic_32& parent;
Core::Memory::Memory& memory;
std::size_t num_interpreted_instructions{};
- static constexpr u64 minimum_run_cycles = 1000U;
+ bool debugger_enabled{};
+ static constexpr u64 minimum_run_cycles = 10000U;
};
std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* page_table) const {
@@ -126,17 +190,21 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
config.callbacks = cb.get();
config.coprocessors[15] = cp15;
config.define_unpredictable_behaviour = true;
- static constexpr std::size_t PAGE_BITS = 12;
- static constexpr std::size_t NUM_PAGE_TABLE_ENTRIES = 1 << (32 - PAGE_BITS);
+ static constexpr std::size_t YUZU_PAGEBITS = 12;
+ static constexpr std::size_t NUM_PAGE_TABLE_ENTRIES = 1 << (32 - YUZU_PAGEBITS);
if (page_table) {
config.page_table = reinterpret_cast<std::array<std::uint8_t*, NUM_PAGE_TABLE_ENTRIES>*>(
page_table->pointers.data());
+ config.absolute_offset_page_table = true;
+ config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS;
+ config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
+ config.only_detect_misalignment_via_page_table_on_page_boundary = true;
+
config.fastmem_pointer = page_table->fastmem_arena;
+
+ config.fastmem_exclusive_access = config.fastmem_pointer != nullptr;
+ config.recompile_on_exclusive_fastmem_failure = true;
}
- config.absolute_offset_page_table = true;
- config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS;
- config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
- config.only_detect_misalignment_via_page_table_on_page_boundary = true;
// Multi-process state
config.processor_id = core_index;
@@ -144,10 +212,21 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
// Timing
config.wall_clock_cntpct = uses_wall_clock;
+ config.enable_cycle_counting = true;
// Code cache size
config.code_cache_size = 512_MiB;
- config.far_code_offset = 400_MiB;
+
+ // Allow memory fault handling to work
+ if (system.DebuggerEnabled()) {
+ config.check_halt_on_memory_access = true;
+ }
+
+ // null_jit
+ if (!page_table) {
+ // Don't waste too much memory on null_jit
+ config.code_cache_size = 8_MiB;
+ }
// Safe optimizations
if (Settings::values.cpu_debug_mode) {
@@ -177,80 +256,101 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
}
if (!Settings::values.cpuopt_fastmem) {
config.fastmem_pointer = nullptr;
+ config.fastmem_exclusive_access = false;
}
- }
-
- // 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_fastmem_exclusives) {
+ config.fastmem_exclusive_access = false;
}
- if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
- config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
+ if (!Settings::values.cpuopt_recompile_exclusives) {
+ config.recompile_on_exclusive_fastmem_failure = false;
}
- if (Settings::values.cpuopt_unsafe_ignore_standard_fpcr) {
- config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
+ } 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;
+ }
}
- if (Settings::values.cpuopt_unsafe_inaccurate_nan) {
+
+ // 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;
}
- }
- // 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;
+ // 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);
}
-void ARM_Dynarmic_32::Run() {
- while (true) {
- jit->Run();
- if (!svc_called) {
- break;
- }
- svc_called = false;
- Kernel::Svc::Call(system, svc_swi);
- if (shutdown) {
- break;
- }
- }
+Dynarmic::HaltReason ARM_Dynarmic_32::RunJit() {
+ return jit.load()->Run();
+}
+
+Dynarmic::HaltReason ARM_Dynarmic_32::StepJit() {
+ return jit.load()->Step();
+}
+
+u32 ARM_Dynarmic_32::GetSvcNumber() const {
+ return svc_swi;
+}
+
+const Kernel::DebugWatchpoint* ARM_Dynarmic_32::HaltedWatchpoint() const {
+ return halted_watchpoint;
}
-void ARM_Dynarmic_32::Step() {
- jit->Step();
+void ARM_Dynarmic_32::RewindBreakpointInstruction() {
+ LoadContext(breakpoint_context);
}
-ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_,
- bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
- std::size_t core_index_)
- : ARM_Interface{system_, interrupt_handlers_, uses_wall_clock_},
- cb(std::make_unique<DynarmicCallbacks32>(*this)),
+ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, bool uses_wall_clock_,
+ ExclusiveMonitor& exclusive_monitor_, std::size_t core_index_)
+ : ARM_Interface{system_, uses_wall_clock_}, 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 {
@@ -260,11 +360,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 {
@@ -285,7 +385,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();
@@ -298,24 +398,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;
+void ARM_Dynarmic_32::SignalInterrupt() {
+ jit.load()->HaltExecution(break_loop);
+}
+
+void ARM_Dynarmic_32::ClearInterrupt() {
+ jit.load()->ClearHalt(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,
@@ -326,13 +429,49 @@ 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 fp, u64 lr, u64 pc) {
+ std::vector<BacktraceEntry> out;
+ auto& memory = system.Memory();
+
+ out.push_back({"", 0, pc, 0, ""});
+
+ // fp (= r11) points to the last frame record.
+ // Frame records are two words long:
+ // fp+0 : pointer to previous frame record
+ // fp+4 : value of lr for frame
+ while (true) {
+ out.push_back({"", 0, lr, 0, ""});
+ if (!fp || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 8)) {
+ break;
+ }
+ lr = memory.Read32(fp + 4);
+ fp = memory.Read32(fp);
+ }
+
+ SymbolicateBacktrace(system, out);
+
+ return out;
+}
+
+std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_32::GetBacktraceFromContext(
+ System& system, const ThreadContext32& ctx) {
+ const auto& reg = ctx.cpu_registers;
+ return GetBacktrace(system, reg[11], reg[14], reg[15]);
+}
+
+std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_32::GetBacktrace() const {
+ return GetBacktrace(system, GetReg(11), GetReg(14), GetReg(15));
}
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h
index 5d47b600d..d24ba2289 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.h
@@ -1,9 +1,9 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include <atomic>
#include <memory>
#include <unordered_map>
@@ -28,20 +28,19 @@ class System;
class ARM_Dynarmic_32 final : public ARM_Interface {
public:
- ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_, bool uses_wall_clock_,
- ExclusiveMonitor& exclusive_monitor_, std::size_t core_index_);
+ ARM_Dynarmic_32(System& system_, bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
+ std::size_t core_index_);
~ARM_Dynarmic_32() override;
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;
void SetVectorReg(int index, u128 value) override;
u32 GetPSTATE() const override;
void SetPSTATE(u32 pstate) override;
- void Run() override;
- void Step() override;
VAddr GetTlsAddress() const override;
void SetTlsAddress(VAddr address) override;
void SetTPIDR_EL0(u64 value) override;
@@ -56,7 +55,8 @@ public:
void LoadContext(const ThreadContext32& ctx) override;
void LoadContext(const ThreadContext64& ctx) override {}
- void PrepareReschedule() override;
+ void SignalInterrupt() override;
+ void ClearInterrupt() override;
void ClearExclusiveState() override;
void ClearInstructionCache() override;
@@ -64,9 +64,23 @@ 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;
+
+protected:
+ Dynarmic::HaltReason RunJit() override;
+ Dynarmic::HaltReason StepJit() override;
+ u32 GetSvcNumber() const override;
+ const Kernel::DebugWatchpoint* HaltedWatchpoint() const override;
+ void RewindBreakpointInstruction() override;
+
private:
std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const;
+ static std::vector<BacktraceEntry> GetBacktrace(Core::System& system, u64 fp, u64 lr, u64 pc);
+
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 +93,18 @@ 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{};
+ // Watchpoint info
+ const Kernel::DebugWatchpoint* halted_watchpoint;
+ ThreadContext32 breakpoint_context;
};
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index 56836bd05..1d46f6d40 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cinttypes>
#include <memory>
@@ -11,11 +10,11 @@
#include "common/logging/log.h"
#include "common/page_table.h"
#include "common/settings.h"
-#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/debugger/debugger.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/svc.h"
@@ -29,61 +28,89 @@ using namespace Common::Literals;
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
public:
explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_)
- : parent{parent_}, memory(parent.system.Memory()) {}
+ : parent{parent_},
+ memory(parent.system.Memory()), debugger_enabled{parent.system.DebuggerEnabled()} {}
u8 MemoryRead8(u64 vaddr) override {
+ CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read);
return memory.Read8(vaddr);
}
u16 MemoryRead16(u64 vaddr) override {
+ CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read);
return memory.Read16(vaddr);
}
u32 MemoryRead32(u64 vaddr) override {
+ CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read);
return memory.Read32(vaddr);
}
u64 MemoryRead64(u64 vaddr) override {
+ CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read);
return memory.Read64(vaddr);
}
Vector MemoryRead128(u64 vaddr) override {
+ CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Read);
return {memory.Read64(vaddr), memory.Read64(vaddr + 8)};
}
+ std::optional<u32> MemoryReadCode(u64 vaddr) override {
+ if (!memory.IsValidVirtualAddressRange(vaddr, sizeof(u32))) {
+ return std::nullopt;
+ }
+ return memory.Read32(vaddr);
+ }
void MemoryWrite8(u64 vaddr, u8 value) override {
- memory.Write8(vaddr, value);
+ if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) {
+ memory.Write8(vaddr, value);
+ }
}
void MemoryWrite16(u64 vaddr, u16 value) override {
- memory.Write16(vaddr, value);
+ if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) {
+ memory.Write16(vaddr, value);
+ }
}
void MemoryWrite32(u64 vaddr, u32 value) override {
- memory.Write32(vaddr, value);
+ if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) {
+ memory.Write32(vaddr, value);
+ }
}
void MemoryWrite64(u64 vaddr, u64 value) override {
- memory.Write64(vaddr, value);
+ if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) {
+ memory.Write64(vaddr, value);
+ }
}
void MemoryWrite128(u64 vaddr, Vector value) override {
- memory.Write64(vaddr, value[0]);
- memory.Write64(vaddr + 8, value[1]);
+ if (CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write)) {
+ memory.Write64(vaddr, value[0]);
+ memory.Write64(vaddr + 8, value[1]);
+ }
}
bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override {
- return memory.WriteExclusive8(vaddr, value, expected);
+ return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) &&
+ memory.WriteExclusive8(vaddr, value, expected);
}
bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override {
- return memory.WriteExclusive16(vaddr, value, expected);
+ return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) &&
+ memory.WriteExclusive16(vaddr, value, expected);
}
bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) override {
- return memory.WriteExclusive32(vaddr, value, expected);
+ return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) &&
+ memory.WriteExclusive32(vaddr, value, expected);
}
bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) override {
- return memory.WriteExclusive64(vaddr, value, expected);
+ return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) &&
+ memory.WriteExclusive64(vaddr, value, expected);
}
bool MemoryWriteExclusive128(u64 vaddr, Vector value, Vector expected) override {
- return memory.WriteExclusive128(vaddr, value, expected);
+ return CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write) &&
+ memory.WriteExclusive128(vaddr, value, expected);
}
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));
+ num_instructions, memory.Read32(pc));
}
void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op,
@@ -93,17 +120,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 {
@@ -114,17 +143,25 @@ public:
case Dynarmic::A64::Exception::SendEventLocal:
case Dynarmic::A64::Exception::Yield:
return;
- case Dynarmic::A64::Exception::Breakpoint:
+ case Dynarmic::A64::Exception::NoExecuteFault:
+ LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#016x}", pc);
+ ReturnException(pc, ARM_Interface::no_execute);
+ return;
default:
- ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
- static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
+ if (debugger_enabled) {
+ ReturnException(pc, ARM_Interface::breakpoint);
+ return;
+ }
+
+ parent.LogBacktrace();
+ LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
+ static_cast<std::size_t>(exception), pc, memory.Read32(pc));
}
}
void CallSVC(u32 swi) override {
- parent.svc_called = true;
parent.svc_swi = swi;
- parent.jit->HaltExecution();
+ parent.jit.load()->HaltExecution(ARM_Interface::svc_call);
}
void AddTicks(u64 ticks) override {
@@ -146,11 +183,12 @@ public:
u64 GetTicksRemaining() override {
if (parent.uses_wall_clock) {
- if (!parent.interrupt_handlers[parent.core_index].IsInterrupted()) {
+ if (!IsInterrupted()) {
return minimum_run_cycles;
}
return 0U;
}
+
return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
}
@@ -158,11 +196,37 @@ public:
return parent.system.CoreTiming().GetClockTicks();
}
+ bool CheckMemoryAccess(VAddr addr, u64 size, Kernel::DebugWatchpointType type) {
+ if (!debugger_enabled) {
+ return true;
+ }
+
+ const auto match{parent.MatchingWatchpoint(addr, size, type)};
+ if (match) {
+ parent.halted_watchpoint = match;
+ parent.jit.load()->HaltExecution(ARM_Interface::watchpoint);
+ return false;
+ }
+
+ return true;
+ }
+
+ void ReturnException(u64 pc, Dynarmic::HaltReason hr) {
+ parent.SaveContext(parent.breakpoint_context);
+ parent.breakpoint_context.pc = pc;
+ parent.jit.load()->HaltExecution(hr);
+ }
+
+ bool IsInterrupted() {
+ return parent.system.Kernel().PhysicalCore(parent.core_index).IsInterrupted();
+ }
+
ARM_Dynarmic_64& parent;
Core::Memory::Memory& memory;
u64 tpidrro_el0 = 0;
u64 tpidr_el0 = 0;
- static constexpr u64 minimum_run_cycles = 1000U;
+ bool debugger_enabled{};
+ static constexpr u64 minimum_run_cycles = 10000U;
};
std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* page_table,
@@ -185,6 +249,9 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
config.fastmem_pointer = page_table->fastmem_arena;
config.fastmem_address_space_bits = address_space_bits;
config.silently_mirror_fastmem = false;
+
+ config.fastmem_exclusive_access = config.fastmem_pointer != nullptr;
+ config.recompile_on_exclusive_fastmem_failure = true;
}
// Multi-process state
@@ -203,10 +270,21 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
// Timing
config.wall_clock_cntpct = uses_wall_clock;
+ config.enable_cycle_counting = true;
// Code cache size
config.code_cache_size = 512_MiB;
- config.far_code_offset = 400_MiB;
+
+ // Allow memory fault handling to work
+ if (system.DebuggerEnabled()) {
+ config.check_halt_on_memory_access = true;
+ }
+
+ // null_jit
+ if (!page_table) {
+ // Don't waste too much memory on null_jit
+ config.code_cache_size = 8_MiB;
+ }
// Safe optimizations
if (Settings::values.cpu_debug_mode) {
@@ -236,95 +314,117 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
}
if (!Settings::values.cpuopt_fastmem) {
config.fastmem_pointer = nullptr;
+ config.fastmem_exclusive_access = false;
}
- }
-
- // 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_fastmem_exclusives) {
+ config.fastmem_exclusive_access = false;
}
- if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
- config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
+ if (!Settings::values.cpuopt_recompile_exclusives) {
+ config.recompile_on_exclusive_fastmem_failure = false;
}
- if (Settings::values.cpuopt_unsafe_inaccurate_nan) {
- config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
+ } 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;
+ }
}
- if (Settings::values.cpuopt_unsafe_fastmem_check) {
+
+ // 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;
}
- }
- // 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;
+ // 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);
}
-void ARM_Dynarmic_64::Run() {
- while (true) {
- jit->Run();
- if (!svc_called) {
- break;
- }
- svc_called = false;
- Kernel::Svc::Call(system, svc_swi);
- if (shutdown) {
- break;
- }
- }
+Dynarmic::HaltReason ARM_Dynarmic_64::RunJit() {
+ return jit.load()->Run();
+}
+
+Dynarmic::HaltReason ARM_Dynarmic_64::StepJit() {
+ return jit.load()->Step();
+}
+
+u32 ARM_Dynarmic_64::GetSvcNumber() const {
+ return svc_swi;
+}
+
+const Kernel::DebugWatchpoint* ARM_Dynarmic_64::HaltedWatchpoint() const {
+ return halted_watchpoint;
}
-void ARM_Dynarmic_64::Step() {
- jit->Step();
+void ARM_Dynarmic_64::RewindBreakpointInstruction() {
+ LoadContext(breakpoint_context);
}
-ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_,
- bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
- std::size_t core_index_)
- : ARM_Interface{system_, interrupt_handlers_, uses_wall_clock_},
+ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, bool uses_wall_clock_,
+ ExclusiveMonitor& exclusive_monitor_, std::size_t core_index_)
+ : ARM_Interface{system_, 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 {
@@ -344,42 +444,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;
+void ARM_Dynarmic_64::SignalInterrupt() {
+ jit.load()->HaltExecution(break_loop);
+}
+
+void ARM_Dynarmic_64::ClearInterrupt() {
+ jit.load()->ClearHalt(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,
@@ -390,13 +495,49 @@ 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, u64 pc) {
+ std::vector<BacktraceEntry> out;
+ auto& memory = system.Memory();
+
+ out.push_back({"", 0, pc, 0, ""});
+
+ // fp (= x29) points to the previous frame record.
+ // 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 || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 16)) {
+ break;
+ }
+ lr = memory.Read64(fp + 8);
+ fp = memory.Read64(fp);
+ }
+
+ SymbolicateBacktrace(system, out);
+
+ return out;
+}
+
+std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_64::GetBacktraceFromContext(
+ System& system, const ThreadContext64& ctx) {
+ const auto& reg = ctx.cpu_registers;
+ return GetBacktrace(system, reg[29], reg[30], ctx.pc);
+}
+
+std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_64::GetBacktrace() const {
+ return GetBacktrace(system, GetReg(29), GetReg(30), GetPC());
}
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h
index 0c4e46c64..ed1a5eb96 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.h
@@ -1,9 +1,9 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include <atomic>
#include <memory>
#include <unordered_map>
@@ -20,26 +20,24 @@ class Memory;
namespace Core {
class DynarmicCallbacks64;
-class CPUInterruptHandler;
class DynarmicExclusiveMonitor;
class System;
class ARM_Dynarmic_64 final : public ARM_Interface {
public:
- ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_, bool uses_wall_clock_,
- ExclusiveMonitor& exclusive_monitor_, std::size_t core_index_);
+ ARM_Dynarmic_64(System& system_, bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
+ std::size_t core_index_);
~ARM_Dynarmic_64() override;
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;
void SetVectorReg(int index, u128 value) override;
u32 GetPSTATE() const override;
void SetPSTATE(u32 pstate) override;
- void Run() override;
- void Step() override;
VAddr GetTlsAddress() const override;
void SetTlsAddress(VAddr address) override;
void SetTPIDR_EL0(u64 value) override;
@@ -50,7 +48,8 @@ public:
void LoadContext(const ThreadContext32& ctx) override {}
void LoadContext(const ThreadContext64& ctx) override;
- void PrepareReschedule() override;
+ void SignalInterrupt() override;
+ void ClearInterrupt() override;
void ClearExclusiveState() override;
void ClearInstructionCache() override;
@@ -58,10 +57,24 @@ 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;
+
+protected:
+ Dynarmic::HaltReason RunJit() override;
+ Dynarmic::HaltReason StepJit() override;
+ u32 GetSvcNumber() const override;
+ const Kernel::DebugWatchpoint* HaltedWatchpoint() const override;
+ void RewindBreakpointInstruction() 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, u64 pc);
+
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 +86,17 @@ 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{};
+ // Breakpoint info
+ const Kernel::DebugWatchpoint* halted_watchpoint;
+ ThreadContext64 breakpoint_context;
};
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
index a043e6735..200efe4db 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/format.h>
#include "common/logging/log.h"
@@ -9,6 +8,10 @@
#include "core/core.h"
#include "core/core_timing.h"
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
using Callback = Dynarmic::A32::Coprocessor::Callback;
using CallbackOrAccessOneWord = Dynarmic::A32::Coprocessor::CallbackOrAccessOneWord;
using CallbackOrAccessTwoWords = Dynarmic::A32::Coprocessor::CallbackOrAccessTwoWords;
@@ -20,7 +23,7 @@ struct fmt::formatter<Dynarmic::A32::CoprocReg> {
}
template <typename FormatContext>
auto format(const Dynarmic::A32::CoprocReg& reg, FormatContext& ctx) {
- return format_to(ctx.out(), "cp{}", static_cast<size_t>(reg));
+ return fmt::format_to(ctx.out(), "cp{}", static_cast<size_t>(reg));
}
};
@@ -48,12 +51,31 @@ CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1
switch (opc2) {
case 4:
// CP15_DATA_SYNC_BARRIER
- // This is a dummy write, we ignore the value written here.
- return &dummy_value;
+ return Callback{
+ [](Dynarmic::A32::Jit*, void*, std::uint32_t, std::uint32_t) -> std::uint64_t {
+#ifdef _MSC_VER
+ _mm_mfence();
+ _mm_lfence();
+#else
+ asm volatile("mfence\n\tlfence\n\t" : : : "memory");
+#endif
+ return 0;
+ },
+ std::nullopt,
+ };
case 5:
// CP15_DATA_MEMORY_BARRIER
- // This is a dummy write, we ignore the value written here.
- return &dummy_value;
+ return Callback{
+ [](Dynarmic::A32::Jit*, void*, std::uint32_t, std::uint32_t) -> std::uint64_t {
+#ifdef _MSC_VER
+ _mm_mfence();
+#else
+ asm volatile("mfence\n\t" : : : "memory");
+#endif
+ return 0;
+ },
+ std::nullopt,
+ };
}
}
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
index f271b2070..d90b3e568 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -36,6 +35,8 @@ public:
ARM_Dynarmic_32& parent;
u32 uprw = 0;
u32 uro = 0;
+
+ friend class ARM_Dynarmic_32;
};
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_exclusive_monitor.cpp b/src/core/arm/dynarmic/arm_exclusive_monitor.cpp
index 397d054a8..fa0c48b25 100644
--- a/src/core/arm/dynarmic/arm_exclusive_monitor.cpp
+++ b/src/core/arm/dynarmic/arm_exclusive_monitor.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#include "core/memory.h"
@@ -37,8 +36,8 @@ u128 DynarmicExclusiveMonitor::ExclusiveRead128(std::size_t core_index, VAddr ad
});
}
-void DynarmicExclusiveMonitor::ClearExclusive() {
- monitor.Clear();
+void DynarmicExclusiveMonitor::ClearExclusive(std::size_t core_index) {
+ monitor.ClearProcessor(core_index);
}
bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) {
diff --git a/src/core/arm/dynarmic/arm_exclusive_monitor.h b/src/core/arm/dynarmic/arm_exclusive_monitor.h
index 265c4ecef..57e6dd0d0 100644
--- a/src/core/arm/dynarmic/arm_exclusive_monitor.h
+++ b/src/core/arm/dynarmic/arm_exclusive_monitor.h
@@ -1,11 +1,8 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <unordered_map>
-
#include <dynarmic/interface/exclusive_monitor.h>
#include "common/common_types.h"
@@ -29,7 +26,7 @@ public:
u32 ExclusiveRead32(std::size_t core_index, VAddr addr) override;
u64 ExclusiveRead64(std::size_t core_index, VAddr addr) override;
u128 ExclusiveRead128(std::size_t core_index, VAddr addr) override;
- void ClearExclusive() override;
+ void ClearExclusive(std::size_t core_index) override;
bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) override;
bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) override;
diff --git a/src/core/arm/exclusive_monitor.cpp b/src/core/arm/exclusive_monitor.cpp
index d8cba369d..2db0b035d 100644
--- a/src/core/arm/exclusive_monitor.cpp
+++ b/src/core/arm/exclusive_monitor.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef ARCHITECTURE_x86_64
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
diff --git a/src/core/arm/exclusive_monitor.h b/src/core/arm/exclusive_monitor.h
index 62f6e6023..15d3c96c1 100644
--- a/src/core/arm/exclusive_monitor.h
+++ b/src/core/arm/exclusive_monitor.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -23,7 +22,7 @@ public:
virtual u32 ExclusiveRead32(std::size_t core_index, VAddr addr) = 0;
virtual u64 ExclusiveRead64(std::size_t core_index, VAddr addr) = 0;
virtual u128 ExclusiveRead128(std::size_t core_index, VAddr addr) = 0;
- virtual void ClearExclusive() = 0;
+ virtual void ClearExclusive(std::size_t core_index) = 0;
virtual bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) = 0;
virtual bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) = 0;
diff --git a/src/core/arm/symbols.cpp b/src/core/arm/symbols.cpp
new file mode 100644
index 000000000..0259c7ea2
--- /dev/null
+++ b/src/core/arm/symbols.cpp
@@ -0,0 +1,130 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/elf.h"
+#include "core/arm/symbols.h"
+#include "core/core.h"
+#include "core/memory.h"
+
+using namespace Common::ELF;
+
+namespace Core {
+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 == ElfDtNull) {
+ break;
+ }
+
+ if (tag == ElfDtStrtab) {
+ string_table_offset = value;
+ } else if (tag == ElfDtSymtab) {
+ symbol_table_offset = value;
+ } else if (tag == ElfDtSyment) {
+ 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.st_name;
+ 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.st_value, symbol.st_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, Elf64_Sym>(ReadBytes);
+ } else {
+ return GetSymbols<u32, Elf32_Sym>(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, Elf64_Sym>(ReadBytes);
+ } else {
+ return GetSymbols<u32, Elf32_Sym>(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..42631b09a
--- /dev/null
+++ b/src/core/arm/symbols.h
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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/constants.cpp b/src/core/constants.cpp
index dccb3e03c..4430173ef 100644
--- a/src/core/constants.cpp
+++ b/src/core/constants.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/constants.h"
diff --git a/src/core/constants.h b/src/core/constants.h
index 81c5cb279..f916ce0b6 100644
--- a/src/core/constants.h
+++ b/src/core/constants.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 3f9a7f44b..1deeee154 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <atomic>
@@ -8,6 +7,7 @@
#include <memory>
#include <utility>
+#include "audio_core/audio_core.h"
#include "common/fs/fs.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
@@ -17,6 +17,7 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
+#include "core/debugger/debugger.h"
#include "core/device_memory.h"
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/mode.h"
@@ -26,9 +27,10 @@
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_real.h"
-#include "core/hardware_interrupt_manager.h"
#include "core/hid/hid_core.h"
+#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
@@ -36,18 +38,19 @@
#include "core/hle/service/apm/apm_controller.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/glue/glue_manager.h"
-#include "core/hle/service/hid/hid.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
#include "core/hle/service/time/time_manager.h"
+#include "core/internal_network/network.h"
#include "core/loader/loader.h"
#include "core/memory.h"
#include "core/memory/cheat_engine.h"
-#include "core/network/network.h"
#include "core/perf_stats.h"
#include "core/reporter.h"
#include "core/telemetry_session.h"
#include "core/tools/freezer.h"
+#include "network/network.h"
+#include "video_core/host1x/host1x.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
@@ -127,7 +130,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
struct System::Impl {
explicit Impl(System& system)
- : kernel{system}, fs_controller{system}, memory{system}, hid_core{},
+ : kernel{system}, fs_controller{system}, memory{system}, hid_core{}, room_network{},
cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {}
SystemResultStatus Run() {
@@ -136,7 +139,6 @@ struct System::Impl {
kernel.Suspend(false);
core_timing.SyncPause(false);
- cpu_manager.Pause(false);
is_paused = false;
return status;
@@ -148,28 +150,34 @@ struct System::Impl {
core_timing.SyncPause(true);
kernel.Suspend(true);
- cpu_manager.Pause(true);
is_paused = true;
return status;
}
- std::unique_lock<std::mutex> StallCPU() {
+ bool IsPaused() const {
+ std::unique_lock lk(suspend_guard);
+ return is_paused;
+ }
+
+ std::unique_lock<std::mutex> StallProcesses() {
std::unique_lock<std::mutex> lk(suspend_guard);
kernel.Suspend(true);
core_timing.SyncPause(true);
- cpu_manager.Pause(true);
return lk;
}
- void UnstallCPU() {
+ void UnstallProcesses() {
if (!is_paused) {
core_timing.SyncPause(false);
kernel.Suspend(false);
- cpu_manager.Pause(false);
}
}
+ void InitializeDebugger(System& system, u16 port) {
+ debugger = std::make_unique<Debugger>(system, port);
+ }
+
SystemResultStatus Init(System& system, Frontend::EmuWindow& emu_window) {
LOG_DEBUG(Core, "initialized OK");
@@ -207,14 +215,16 @@ struct System::Impl {
telemetry_session = std::make_unique<Core::TelemetrySession>();
+ host1x_core = std::make_unique<Tegra::Host1x::Host1x>(system);
gpu_core = VideoCore::CreateGPU(emu_window, system);
if (!gpu_core) {
return SystemResultStatus::ErrorVideoCore;
}
+ audio_core = std::make_unique<AudioCore::AudioCore>(system);
+
service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
services = std::make_unique<Service::Services>(service_manager, system);
- interrupt_manager = std::make_unique<Hardware::InterruptManager>(system);
// Initialize time manager, which must happen after kernel is created
time_manager.Initialize();
@@ -252,9 +262,16 @@ struct System::Impl {
}
telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
+
+ // Create a resource limit for the process.
+ const auto physical_memory_size =
+ kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application);
+ auto* resource_limit = Kernel::CreateResourceLimitForProcess(system, physical_memory_size);
+
+ // Create the process.
auto main_process = Kernel::KProcess::Create(system.Kernel());
ASSERT(Kernel::KProcess::Initialize(main_process, system, "main",
- Kernel::KProcess::ProcessType::Userland)
+ Kernel::KProcess::ProcessType::Userland, resource_limit)
.IsSuccess());
const auto [load_result, load_parameters] = app_loader->Load(*main_process, system);
if (load_result != Loader::ResultStatus::Success) {
@@ -281,7 +298,7 @@ struct System::Impl {
if (Settings::values.gamecard_current_game) {
fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, filepath));
} else if (!Settings::values.gamecard_path.GetValue().empty()) {
- const auto gamecard_path = Settings::values.gamecard_path.GetValue();
+ const auto& gamecard_path = Settings::values.gamecard_path.GetValue();
fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, gamecard_path));
}
}
@@ -294,11 +311,33 @@ struct System::Impl {
GetAndResetPerfStats();
perf_stats->BeginSystemFrame();
+ std::string name = "Unknown Game";
+ if (app_loader->ReadTitle(name) != Loader::ResultStatus::Success) {
+ LOG_ERROR(Core, "Failed to read title for ROM (Error {})", load_result);
+ }
+
+ std::string title_version;
+ const FileSys::PatchManager pm(program_id, system.GetFileSystemController(),
+ system.GetContentProvider());
+ const auto metadata = pm.GetControlMetadata();
+ if (metadata.first != nullptr) {
+ title_version = metadata.first->GetVersionString();
+ }
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ Network::GameInfo game_info;
+ game_info.name = name;
+ game_info.id = program_id;
+ game_info.version = title_version;
+ room_member->SendGameInfo(game_info);
+ }
+
status = SystemResultStatus::Success;
return status;
}
void Shutdown() {
+ SetShuttingDown(true);
+
// Log last frame performance stats if game was loded
if (perf_stats) {
const auto perf_results = GetAndResetPerfStats();
@@ -317,25 +356,45 @@ struct System::Impl {
is_powered_on = false;
exit_lock = false;
- gpu_core->NotifyShutdown();
+ if (gpu_core != nullptr) {
+ gpu_core->NotifyShutdown();
+ }
+ kernel.ShutdownCores();
+ cpu_manager.Shutdown();
+ debugger.reset();
+ kernel.CloseServices();
services.reset();
service_manager.reset();
cheat_engine.reset();
telemetry_session.reset();
- cpu_manager.Shutdown();
time_manager.Shutdown();
core_timing.Shutdown();
app_loader.reset();
+ audio_core.reset();
gpu_core.reset();
+ host1x_core.reset();
perf_stats.reset();
kernel.Shutdown();
memory.Reset();
applet_manager.ClearAll();
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ Network::GameInfo game_info{};
+ room_member->SendGameInfo(game_info);
+ }
+
LOG_DEBUG(Core, "Shutdown OK");
}
+ bool IsShuttingDown() const {
+ return is_shutting_down;
+ }
+
+ void SetShuttingDown(bool shutting_down) {
+ is_shutting_down = shutting_down;
+ }
+
Loader::ResultStatus GetGameName(std::string& out) const {
if (app_loader == nullptr)
return Loader::ResultStatus::ErrorNotInitialized;
@@ -378,8 +437,9 @@ struct System::Impl {
return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs());
}
- std::mutex suspend_guard;
+ mutable std::mutex suspend_guard;
bool is_paused{};
+ std::atomic<bool> is_shutting_down{};
Timing::CoreTiming core_timing;
Kernel::KernelCore kernel;
@@ -391,10 +451,13 @@ struct System::Impl {
/// AppLoader used to load the current executing application
std::unique_ptr<Loader::AppLoader> app_loader;
std::unique_ptr<Tegra::GPU> gpu_core;
- std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
+ std::unique_ptr<Tegra::Host1x::Host1x> host1x_core;
std::unique_ptr<Core::DeviceMemory> device_memory;
+ std::unique_ptr<AudioCore::AudioCore> audio_core;
Core::Memory::Memory memory;
Core::HID::HIDCore hid_core;
+ Network::RoomNetwork room_network;
+
CpuManager cpu_manager;
std::atomic_bool is_powered_on{};
bool exit_lock = false;
@@ -426,6 +489,9 @@ struct System::Impl {
/// Network instance
Network::NetworkInstance network_instance;
+ /// Debugger
+ std::unique_ptr<Core::Debugger> debugger;
+
SystemResultStatus status = SystemResultStatus::Success;
std::string status_details = "";
@@ -462,8 +528,8 @@ SystemResultStatus System::Pause() {
return impl->Pause();
}
-SystemResultStatus System::SingleStep() {
- return SystemResultStatus::Success;
+bool System::IsPaused() const {
+ return impl->IsPaused();
}
void System::InvalidateCpuInstructionCaches() {
@@ -478,12 +544,30 @@ void System::Shutdown() {
impl->Shutdown();
}
-std::unique_lock<std::mutex> System::StallCPU() {
- return impl->StallCPU();
+bool System::IsShuttingDown() const {
+ return impl->IsShuttingDown();
}
-void System::UnstallCPU() {
- impl->UnstallCPU();
+void System::SetShuttingDown(bool shutting_down) {
+ impl->SetShuttingDown(shutting_down);
+}
+
+void System::DetachDebugger() {
+ if (impl->debugger) {
+ impl->debugger->NotifyShutdown();
+ }
+}
+
+std::unique_lock<std::mutex> System::StallProcesses() {
+ return impl->StallProcesses();
+}
+
+void System::UnstallProcesses() {
+ impl->UnstallProcesses();
+}
+
+void System::InitializeDebugger() {
+ impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
}
SystemResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
@@ -495,10 +579,6 @@ bool System::IsPoweredOn() const {
return impl->is_powered_on.load(std::memory_order::relaxed);
}
-void System::PrepareReschedule() {
- // Deprecated, does nothing, kept for backward compatibility.
-}
-
void System::PrepareReschedule(const u32 core_index) {
impl->kernel.PrepareReschedule(core_index);
}
@@ -589,12 +669,12 @@ const Tegra::GPU& System::GPU() const {
return *impl->gpu_core;
}
-Core::Hardware::InterruptManager& System::InterruptManager() {
- return *impl->interrupt_manager;
+Tegra::Host1x::Host1x& System::Host1x() {
+ return *impl->host1x_core;
}
-const Core::Hardware::InterruptManager& System::InterruptManager() const {
- return *impl->interrupt_manager;
+const Tegra::Host1x::Host1x& System::Host1x() const {
+ return *impl->host1x_core;
}
VideoCore::RendererBase& System::Renderer() {
@@ -621,6 +701,14 @@ const HID::HIDCore& System::HIDCore() const {
return impl->hid_core;
}
+AudioCore::AudioCore& System::AudioCore() {
+ return *impl->audio_core;
+}
+
+const AudioCore::AudioCore& System::AudioCore() const {
+ return *impl->audio_core;
+}
+
Timing::CoreTiming& System::CoreTiming() {
return impl->core_timing;
}
@@ -803,6 +891,26 @@ bool System::IsMulticore() const {
return impl->is_multicore;
}
+bool System::DebuggerEnabled() const {
+ return Settings::values.use_gdbstub.GetValue();
+}
+
+Core::Debugger& System::GetDebugger() {
+ return *impl->debugger;
+}
+
+const Core::Debugger& System::GetDebugger() const {
+ return *impl->debugger;
+}
+
+Network::RoomNetwork& System::GetRoomNetwork() {
+ return impl->room_network;
+}
+
+const Network::RoomNetwork& System::GetRoomNetwork() const {
+ return impl->room_network;
+}
+
void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) {
impl->execute_program_callback = std::move(callback);
}
diff --git a/src/core/core.h b/src/core/core.h
index 52ff90359..7843cc8ad 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -75,28 +74,36 @@ class TimeManager;
namespace Tegra {
class DebugContext;
class GPU;
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
} // namespace Tegra
namespace VideoCore {
class RendererBase;
} // namespace VideoCore
+namespace AudioCore {
+class AudioCore;
+} // namespace AudioCore
+
namespace Core::Timing {
class CoreTiming;
}
-namespace Core::Hardware {
-class InterruptManager;
-}
-
namespace Core::HID {
class HIDCore;
}
+namespace Network {
+class RoomNetwork;
+}
+
namespace Core {
class ARM_Interface;
class CpuManager;
+class Debugger;
class DeviceMemory;
class ExclusiveMonitor;
class SpeedLimiter;
@@ -147,11 +154,8 @@ public:
*/
[[nodiscard]] SystemResultStatus Pause();
- /**
- * Step the CPU one instruction
- * @return Result status, indicating whether or not the operation succeeded.
- */
- [[nodiscard]] SystemResultStatus SingleStep();
+ /// Check if the core is currently paused.
+ [[nodiscard]] bool IsPaused() const;
/**
* Invalidate the CPU instruction caches
@@ -165,8 +169,22 @@ public:
/// Shutdown the emulated system.
void Shutdown();
- std::unique_lock<std::mutex> StallCPU();
- void UnstallCPU();
+ /// Check if the core is shutting down.
+ [[nodiscard]] bool IsShuttingDown() const;
+
+ /// Set the shutting down state.
+ void SetShuttingDown(bool shutting_down);
+
+ /// Forcibly detach the debugger if it is running.
+ void DetachDebugger();
+
+ std::unique_lock<std::mutex> StallProcesses();
+ void UnstallProcesses();
+
+ /**
+ * Initialize the debugger.
+ */
+ void InitializeDebugger();
/**
* Load an executable application.
@@ -194,9 +212,6 @@ public:
[[nodiscard]] const Core::TelemetrySession& TelemetrySession() const;
/// Prepare the core emulation for a reschedule
- void PrepareReschedule();
-
- /// Prepare the core emulation for a reschedule
void PrepareReschedule(u32 core_index);
/// Gets and resets core performance statistics
@@ -244,12 +259,24 @@ public:
/// Gets an immutable reference to the GPU interface.
[[nodiscard]] const Tegra::GPU& GPU() const;
+ /// Gets a mutable reference to the Host1x interface
+ [[nodiscard]] Tegra::Host1x::Host1x& Host1x();
+
+ /// Gets an immutable reference to the Host1x interface.
+ [[nodiscard]] const Tegra::Host1x::Host1x& Host1x() const;
+
/// Gets a mutable reference to the renderer.
[[nodiscard]] VideoCore::RendererBase& Renderer();
/// Gets an immutable reference to the renderer.
[[nodiscard]] const VideoCore::RendererBase& Renderer() const;
+ /// Gets a mutable reference to the audio interface
+ [[nodiscard]] AudioCore::AudioCore& AudioCore();
+
+ /// Gets an immutable reference to the audio interface.
+ [[nodiscard]] const AudioCore::AudioCore& AudioCore() const;
+
/// Gets the global scheduler
[[nodiscard]] Kernel::GlobalSchedulerContext& GlobalSchedulerContext();
@@ -274,12 +301,6 @@ public:
/// Provides a constant reference to the core timing instance.
[[nodiscard]] const Timing::CoreTiming& CoreTiming() const;
- /// Provides a reference to the interrupt manager instance.
- [[nodiscard]] Core::Hardware::InterruptManager& InterruptManager();
-
- /// Provides a constant reference to the interrupt manager instance.
- [[nodiscard]] const Core::Hardware::InterruptManager& InterruptManager() const;
-
/// Provides a reference to the kernel instance.
[[nodiscard]] Kernel::KernelCore& Kernel();
@@ -357,6 +378,15 @@ public:
[[nodiscard]] Service::Time::TimeManager& GetTimeManager();
[[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const;
+ [[nodiscard]] Core::Debugger& GetDebugger();
+ [[nodiscard]] const Core::Debugger& GetDebugger() const;
+
+ /// Gets a mutable reference to the Room Network.
+ [[nodiscard]] Network::RoomNetwork& GetRoomNetwork();
+
+ /// Gets an immutable reference to the Room Network.
+ [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
+
void SetExitLock(bool locked);
[[nodiscard]] bool GetExitLock() const;
@@ -378,6 +408,9 @@ public:
/// Tells if system is running on multicore.
[[nodiscard]] bool IsMulticore() const;
+ /// Tells if the system debugger is enabled.
+ [[nodiscard]] bool DebuggerEnabled() const;
+
/// Type used for the frontend to designate a callback for System to re-launch the application
/// using a specified program index.
using ExecuteProgramCallback = std::function<void(std::size_t)>;
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index c2f0f609f..6c0fcb7b5 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <mutex>
@@ -21,10 +20,11 @@ std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callbac
}
struct CoreTiming::Event {
- u64 time;
+ s64 time;
u64 fifo_order;
std::uintptr_t user_data;
std::weak_ptr<EventType> type;
+ s64 reschedule_time;
// Sort by time, unless the times are the same, in which case sort by
// the order added to the queue
@@ -43,10 +43,10 @@ CoreTiming::CoreTiming()
CoreTiming::~CoreTiming() = default;
void CoreTiming::ThreadEntry(CoreTiming& instance) {
- constexpr char name[] = "yuzu:HostTiming";
+ constexpr char name[] = "HostTiming";
MicroProfileOnThreadCreate(name);
Common::SetCurrentThreadName(name);
- Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
instance.on_thread_init();
instance.ThreadLoop();
MicroProfileOnThreadExit();
@@ -57,7 +57,8 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
event_fifo_id = 0;
shutting_down = false;
ticks = 0;
- const auto empty_timed_callback = [](std::uintptr_t, std::chrono::nanoseconds) {};
+ const auto empty_timed_callback = [](std::uintptr_t, u64, std::chrono::nanoseconds)
+ -> std::optional<std::chrono::nanoseconds> { return std::nullopt; };
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
if (is_multicore) {
timer_thread = std::make_unique<std::thread>(ThreadEntry, std::ref(*this));
@@ -80,12 +81,17 @@ void CoreTiming::Shutdown() {
void CoreTiming::Pause(bool is_paused) {
paused = is_paused;
pause_event.Set();
+
+ if (!is_paused) {
+ pause_end_time = GetGlobalTimeNs().count();
+ }
}
void CoreTiming::SyncPause(bool is_paused) {
if (is_paused == paused && paused_set == paused) {
return;
}
+
Pause(is_paused);
if (timer_thread) {
if (!is_paused) {
@@ -95,6 +101,10 @@ void CoreTiming::SyncPause(bool is_paused) {
while (paused_set != is_paused)
;
}
+
+ if (!is_paused) {
+ pause_end_time = GetGlobalTimeNs().count();
+ }
}
bool CoreTiming::IsRunning() const {
@@ -107,15 +117,33 @@ bool CoreTiming::HasPendingEvents() const {
void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
const std::shared_ptr<EventType>& event_type,
- std::uintptr_t user_data) {
+ std::uintptr_t user_data, bool absolute_time) {
{
std::scoped_lock scope{basic_lock};
- const u64 timeout = static_cast<u64>((GetGlobalTimeNs() + ns_into_future).count());
+ const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
+
+ event_queue.emplace_back(
+ Event{next_time.count(), event_fifo_id++, user_data, event_type, 0});
+ std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+ }
- event_queue.emplace_back(Event{timeout, event_fifo_id++, user_data, event_type});
+ event.Set();
+}
+
+void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
+ std::chrono::nanoseconds resched_time,
+ const std::shared_ptr<EventType>& event_type,
+ std::uintptr_t user_data, bool absolute_time) {
+ {
+ std::scoped_lock scope{basic_lock};
+ const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
+
+ event_queue.emplace_back(
+ Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
+
event.Set();
}
@@ -194,20 +222,39 @@ std::optional<s64> CoreTiming::Advance() {
Event evt = std::move(event_queue.front());
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
event_queue.pop_back();
- basic_lock.unlock();
if (const auto event_type{evt.type.lock()}) {
- event_type->callback(
- evt.user_data, std::chrono::nanoseconds{static_cast<s64>(global_timer - evt.time)});
+ basic_lock.unlock();
+
+ const auto new_schedule_time{event_type->callback(
+ evt.user_data, evt.time,
+ std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
+
+ basic_lock.lock();
+
+ if (evt.reschedule_time != 0) {
+ const auto next_schedule_time{new_schedule_time.has_value()
+ ? new_schedule_time.value().count()
+ : evt.reschedule_time};
+
+ // If this event was scheduled into a pause, its time now is going to be way behind.
+ // Re-set this event to continue from the end of the pause.
+ auto next_time{evt.time + next_schedule_time};
+ if (evt.time < pause_end_time) {
+ next_time = pause_end_time + next_schedule_time;
+ }
+
+ event_queue.emplace_back(
+ Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time});
+ std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+ }
}
- basic_lock.lock();
global_timer = GetGlobalTimeNs().count();
}
if (!event_queue.empty()) {
- const s64 next_time = event_queue.front().time - global_timer;
- return next_time;
+ return event_queue.front().time;
} else {
return std::nullopt;
}
@@ -220,16 +267,35 @@ void CoreTiming::ThreadLoop() {
paused_set = false;
const auto next_time = Advance();
if (next_time) {
- if (*next_time > 0) {
- std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time);
- event.WaitFor(next_time_ns);
+ // There are more events left in the queue, wait until the next event.
+ const auto wait_time = *next_time - GetGlobalTimeNs().count();
+ if (wait_time > 0) {
+ // Assume a timer resolution of 1ms.
+ static constexpr s64 TimerResolutionNS = 1000000;
+
+ // Sleep in discrete intervals of the timer resolution, and spin the rest.
+ const auto sleep_time = wait_time - (wait_time % TimerResolutionNS);
+ if (sleep_time > 0) {
+ event.WaitFor(std::chrono::nanoseconds(sleep_time));
+ }
+
+ while (!paused && !event.IsSet() && GetGlobalTimeNs().count() < *next_time) {
+ // Yield to reduce thread starvation.
+ std::this_thread::yield();
+ }
+
+ if (event.IsSet()) {
+ event.Reset();
+ }
}
} else {
+ // Queue is empty, wait until another event is scheduled and signals us to continue.
wait_set = true;
event.Wait();
}
wait_set = false;
}
+
paused_set = true;
clock->Pause(true);
pause_event.Wait();
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 888828fd0..3259397b2 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,21 +7,21 @@
#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"
namespace Core::Timing {
/// A callback that may be scheduled for a particular core timing event.
-using TimedCallback =
- std::function<void(std::uintptr_t user_data, std::chrono::nanoseconds ns_late)>;
+using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>(
+ std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>;
/// Contains the characteristics of a particular event.
struct EventType {
@@ -94,7 +93,15 @@ public:
/// Schedules an event in core timing
void ScheduleEvent(std::chrono::nanoseconds ns_into_future,
- const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data = 0);
+ const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data = 0,
+ bool absolute_time = false);
+
+ /// Schedules an event which will automatically re-schedule itself with the given time, until
+ /// unscheduled
+ void ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
+ std::chrono::nanoseconds resched_time,
+ const std::shared_ptr<EventType>& event_type,
+ std::uintptr_t user_data = 0, bool absolute_time = false);
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data);
@@ -137,7 +144,7 @@ private:
std::unique_ptr<Common::WallClock> clock;
- u64 global_timer = 0;
+ s64 global_timer = 0;
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
// We don't use std::priority_queue because we need to be able to serialize, unserialize and
@@ -149,8 +156,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{};
@@ -160,6 +167,7 @@ private:
std::function<void()> on_thread_init{};
bool is_multicore{};
+ s64 pause_end_time{};
/// Cycle timing
u64 ticks{};
diff --git a/src/core/core_timing_util.h b/src/core/core_timing_util.h
index 14c36a485..fe5aaefc7 100644
--- a/src/core/core_timing_util.h
+++ b/src/core/core_timing_util.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
index cbcc54891..0dd4c2196 100644
--- a/src/core/cpu_manager.cpp
+++ b/src/core/cpu_manager.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/fiber.h"
#include "common/microprofile.h"
@@ -9,6 +8,7 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
+#include "core/hle/kernel/k_interrupt_manager.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
@@ -22,75 +22,51 @@ CpuManager::~CpuManager() = default;
void CpuManager::ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager,
std::size_t core) {
- cpu_manager.RunThread(stop_token, core);
+ cpu_manager.RunThread(core);
}
void CpuManager::Initialize() {
- running_mode = true;
- if (is_multicore) {
- for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- core_data[core].host_thread = std::jthread(ThreadStart, std::ref(*this), core);
- }
- } else {
- core_data[0].host_thread = std::jthread(ThreadStart, std::ref(*this), 0);
+ num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
+ gpu_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
+
+ for (std::size_t core = 0; core < num_cores; core++) {
+ core_data[core].host_thread = std::jthread(ThreadStart, std::ref(*this), core);
}
}
void CpuManager::Shutdown() {
- running_mode = false;
- Pause(false);
-}
-
-std::function<void(void*)> CpuManager::GetGuestThreadStartFunc() {
- return GuestThreadFunction;
-}
-
-std::function<void(void*)> CpuManager::GetIdleThreadStartFunc() {
- return IdleThreadFunction;
-}
-
-std::function<void(void*)> CpuManager::GetSuspendThreadStartFunc() {
- return SuspendThreadFunction;
-}
-
-void CpuManager::GuestThreadFunction(void* cpu_manager_) {
- CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
- if (cpu_manager->is_multicore) {
- cpu_manager->MultiCoreRunGuestThread();
- } else {
- cpu_manager->SingleCoreRunGuestThread();
+ for (std::size_t core = 0; core < num_cores; core++) {
+ if (core_data[core].host_thread.joinable()) {
+ core_data[core].host_thread.join();
+ }
}
}
-void CpuManager::GuestRewindFunction(void* cpu_manager_) {
- CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
- if (cpu_manager->is_multicore) {
- cpu_manager->MultiCoreRunGuestLoop();
+void CpuManager::GuestThreadFunction() {
+ if (is_multicore) {
+ MultiCoreRunGuestThread();
} else {
- cpu_manager->SingleCoreRunGuestLoop();
+ SingleCoreRunGuestThread();
}
}
-void CpuManager::IdleThreadFunction(void* cpu_manager_) {
- CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
- if (cpu_manager->is_multicore) {
- cpu_manager->MultiCoreRunIdleThread();
+void CpuManager::IdleThreadFunction() {
+ if (is_multicore) {
+ MultiCoreRunIdleThread();
} else {
- cpu_manager->SingleCoreRunIdleThread();
+ SingleCoreRunIdleThread();
}
}
-void CpuManager::SuspendThreadFunction(void* cpu_manager_) {
- CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
- if (cpu_manager->is_multicore) {
- cpu_manager->MultiCoreRunSuspendThread();
- } else {
- cpu_manager->SingleCoreRunSuspendThread();
- }
+void CpuManager::ShutdownThreadFunction() {
+ ShutdownThread();
}
-void* CpuManager::GetStartFuncParamater() {
- return static_cast<void*>(this);
+void CpuManager::HandleInterrupt() {
+ auto& kernel = system.Kernel();
+ auto core_index = kernel.CurrentPhysicalCoreIndex();
+
+ Kernel::KInterruptManager::HandleInterrupt(kernel, static_cast<s32>(core_index));
}
///////////////////////////////////////////////////////////////////////////////
@@ -98,90 +74,37 @@ void* CpuManager::GetStartFuncParamater() {
///////////////////////////////////////////////////////////////////////////////
void CpuManager::MultiCoreRunGuestThread() {
+ // Similar to UserModeThreadStarter in HOS
auto& kernel = system.Kernel();
kernel.CurrentScheduler()->OnThreadStart();
- auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
- auto& host_context = thread->GetHostContext();
- host_context->SetRewindPoint(GuestRewindFunction, this);
- MultiCoreRunGuestLoop();
-}
-
-void CpuManager::MultiCoreRunGuestLoop() {
- auto& kernel = system.Kernel();
while (true) {
auto* physical_core = &kernel.CurrentPhysicalCore();
- system.EnterDynarmicProfile();
while (!physical_core->IsInterrupted()) {
physical_core->Run();
physical_core = &kernel.CurrentPhysicalCore();
}
- system.ExitDynarmicProfile();
- {
- Kernel::KScopedDisableDispatch dd(kernel);
- physical_core->ArmInterface().ClearExclusiveState();
- }
+
+ HandleInterrupt();
}
}
void CpuManager::MultiCoreRunIdleThread() {
- auto& kernel = system.Kernel();
- while (true) {
- Kernel::KScopedDisableDispatch dd(kernel);
- kernel.CurrentPhysicalCore().Idle();
- }
-}
+ // Not accurate to HOS. Remove this entire method when singlecore is removed.
+ // See notes in KScheduler::ScheduleImpl for more information about why this
+ // is inaccurate.
-void CpuManager::MultiCoreRunSuspendThread() {
auto& kernel = system.Kernel();
kernel.CurrentScheduler()->OnThreadStart();
- while (true) {
- auto core = kernel.CurrentPhysicalCoreIndex();
- auto& scheduler = *kernel.CurrentScheduler();
- Kernel::KThread* current_thread = scheduler.GetCurrentThread();
- Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context);
- ASSERT(scheduler.ContextSwitchPending());
- ASSERT(core == kernel.CurrentPhysicalCoreIndex());
- scheduler.RescheduleCurrentCore();
- }
-}
-void CpuManager::MultiCorePause(bool paused) {
- if (!paused) {
- bool all_not_barrier = false;
- while (!all_not_barrier) {
- all_not_barrier = true;
- for (const auto& data : core_data) {
- all_not_barrier &= !data.is_running.load() && data.initialized.load();
- }
- }
- for (auto& data : core_data) {
- data.enter_barrier->Set();
- }
- if (paused_state.load()) {
- bool all_barrier = false;
- while (!all_barrier) {
- all_barrier = true;
- for (const auto& data : core_data) {
- all_barrier &= data.is_paused.load() && data.initialized.load();
- }
- }
- for (auto& data : core_data) {
- data.exit_barrier->Set();
- }
- }
- } else {
- /// Wait until all cores are paused.
- bool all_barrier = false;
- while (!all_barrier) {
- all_barrier = true;
- for (const auto& data : core_data) {
- all_barrier &= data.is_paused.load() && data.initialized.load();
- }
+ while (true) {
+ auto& physical_core = kernel.CurrentPhysicalCore();
+ if (!physical_core.IsInterrupted()) {
+ physical_core.Idle();
}
- /// Don't release the barrier
+
+ HandleInterrupt();
}
- paused_state = paused;
}
///////////////////////////////////////////////////////////////////////////////
@@ -191,175 +114,110 @@ void CpuManager::MultiCorePause(bool paused) {
void CpuManager::SingleCoreRunGuestThread() {
auto& kernel = system.Kernel();
kernel.CurrentScheduler()->OnThreadStart();
- auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
- auto& host_context = thread->GetHostContext();
- host_context->SetRewindPoint(GuestRewindFunction, this);
- SingleCoreRunGuestLoop();
-}
-void CpuManager::SingleCoreRunGuestLoop() {
- auto& kernel = system.Kernel();
while (true) {
auto* physical_core = &kernel.CurrentPhysicalCore();
- system.EnterDynarmicProfile();
if (!physical_core->IsInterrupted()) {
physical_core->Run();
physical_core = &kernel.CurrentPhysicalCore();
}
- system.ExitDynarmicProfile();
+
kernel.SetIsPhantomModeForSingleCore(true);
system.CoreTiming().Advance();
kernel.SetIsPhantomModeForSingleCore(false);
- physical_core->ArmInterface().ClearExclusiveState();
+
PreemptSingleCore();
- auto& scheduler = kernel.Scheduler(current_core);
- scheduler.RescheduleCurrentCore();
+ HandleInterrupt();
}
}
void CpuManager::SingleCoreRunIdleThread() {
auto& kernel = system.Kernel();
+ kernel.CurrentScheduler()->OnThreadStart();
+
while (true) {
- auto& physical_core = kernel.CurrentPhysicalCore();
PreemptSingleCore(false);
system.CoreTiming().AddTicks(1000U);
idle_count++;
- auto& scheduler = physical_core.Scheduler();
- scheduler.RescheduleCurrentCore();
+ HandleInterrupt();
}
}
-void CpuManager::SingleCoreRunSuspendThread() {
+void CpuManager::PreemptSingleCore(bool from_running_environment) {
auto& kernel = system.Kernel();
- kernel.CurrentScheduler()->OnThreadStart();
- while (true) {
- auto core = kernel.GetCurrentHostThreadID();
- auto& scheduler = *kernel.CurrentScheduler();
- Kernel::KThread* current_thread = scheduler.GetCurrentThread();
- Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[0].host_context);
- ASSERT(scheduler.ContextSwitchPending());
- ASSERT(core == kernel.GetCurrentHostThreadID());
- scheduler.RescheduleCurrentCore();
- }
-}
-void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
- {
- auto& kernel = system.Kernel();
- auto& scheduler = kernel.Scheduler(current_core);
- Kernel::KThread* current_thread = scheduler.GetCurrentThread();
- if (idle_count >= 4 || from_running_enviroment) {
- if (!from_running_enviroment) {
- system.CoreTiming().Idle();
- idle_count = 0;
- }
- kernel.SetIsPhantomModeForSingleCore(true);
- system.CoreTiming().Advance();
- kernel.SetIsPhantomModeForSingleCore(false);
+ if (idle_count >= 4 || from_running_environment) {
+ if (!from_running_environment) {
+ system.CoreTiming().Idle();
+ idle_count = 0;
}
- current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
- system.CoreTiming().ResetTicks();
- scheduler.Unload(scheduler.GetCurrentThread());
-
- auto& next_scheduler = kernel.Scheduler(current_core);
- Common::Fiber::YieldTo(current_thread->GetHostContext(), *next_scheduler.ControlContext());
+ kernel.SetIsPhantomModeForSingleCore(true);
+ system.CoreTiming().Advance();
+ kernel.SetIsPhantomModeForSingleCore(false);
}
+ current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
+ system.CoreTiming().ResetTicks();
+ kernel.Scheduler(current_core).PreemptSingleCore();
- // May have changed scheduler
- {
- auto& scheduler = system.Kernel().Scheduler(current_core);
- scheduler.Reload(scheduler.GetCurrentThread());
- if (!scheduler.IsIdle()) {
- idle_count = 0;
- }
+ // We've now been scheduled again, and we may have exchanged schedulers.
+ // Reload the scheduler in case it's different.
+ if (!kernel.Scheduler(current_core).IsIdle()) {
+ idle_count = 0;
}
}
-void CpuManager::SingleCorePause(bool paused) {
- if (!paused) {
- bool all_not_barrier = false;
- while (!all_not_barrier) {
- all_not_barrier = !core_data[0].is_running.load() && core_data[0].initialized.load();
- }
- core_data[0].enter_barrier->Set();
- if (paused_state.load()) {
- bool all_barrier = false;
- while (!all_barrier) {
- all_barrier = core_data[0].is_paused.load() && core_data[0].initialized.load();
- }
- core_data[0].exit_barrier->Set();
- }
- } else {
- /// Wait until all cores are paused.
- bool all_barrier = false;
- while (!all_barrier) {
- all_barrier = core_data[0].is_paused.load() && core_data[0].initialized.load();
- }
- /// Don't release the barrier
- }
- paused_state = paused;
+void CpuManager::GuestActivate() {
+ // Similar to the HorizonKernelMain callback in HOS
+ auto& kernel = system.Kernel();
+ auto* scheduler = kernel.CurrentScheduler();
+
+ scheduler->Activate();
+ UNREACHABLE();
}
-void CpuManager::Pause(bool paused) {
- if (is_multicore) {
- MultiCorePause(paused);
- } else {
- SingleCorePause(paused);
- }
+void CpuManager::ShutdownThread() {
+ auto& kernel = system.Kernel();
+ auto* thread = kernel.GetCurrentEmuThread();
+ auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0;
+
+ Common::Fiber::YieldTo(thread->GetHostContext(), *core_data[core].host_context);
+ UNREACHABLE();
}
-void CpuManager::RunThread(std::stop_token stop_token, std::size_t core) {
+void CpuManager::RunThread(std::size_t core) {
/// Initialization
system.RegisterCoreThread(core);
std::string name;
if (is_multicore) {
- name = "yuzu:CPUCore_" + std::to_string(core);
+ name = "CPUCore_" + std::to_string(core);
} else {
- name = "yuzu:CPUThread";
+ name = "CPUThread";
}
MicroProfileOnThreadCreate(name.c_str());
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
auto& data = core_data[core];
- data.enter_barrier = std::make_unique<Common::Event>();
- data.exit_barrier = std::make_unique<Common::Event>();
data.host_context = Common::Fiber::ThreadToFiber();
- data.is_running = false;
- data.initialized = true;
- const bool sc_sync = !is_async_gpu && !is_multicore;
- bool sc_sync_first_use = sc_sync;
// Cleanup
SCOPE_EXIT({
data.host_context->Exit();
- data.enter_barrier.reset();
- data.exit_barrier.reset();
- data.initialized = false;
MicroProfileOnThreadExit();
});
- /// Running
- while (running_mode) {
- data.is_running = false;
- data.enter_barrier->Wait();
- if (sc_sync_first_use) {
- system.GPU().ObtainContext();
- sc_sync_first_use = false;
- }
+ // Running
+ gpu_barrier->Sync();
- // Emulation was stopped
- if (stop_token.stop_requested()) {
- return;
- }
-
- auto current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
- data.is_running = true;
- Common::Fiber::YieldTo(data.host_context, *current_thread->GetHostContext());
- data.is_running = false;
- data.is_paused = true;
- data.exit_barrier->Wait();
- data.is_paused = false;
+ if (!is_async_gpu && !is_multicore) {
+ system.GPU().ObtainContext();
}
+
+ auto& kernel = system.Kernel();
+ auto& scheduler = *kernel.CurrentScheduler();
+ auto* thread = scheduler.GetSchedulerCurrentThread();
+ Kernel::SetCurrentThread(kernel, thread);
+
+ Common::Fiber::YieldTo(data.host_context, *thread->GetHostContext());
}
} // namespace Core
diff --git a/src/core/cpu_manager.h b/src/core/cpu_manager.h
index 9d92d4af0..95ea3ef39 100644
--- a/src/core/cpu_manager.h
+++ b/src/core/cpu_manager.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -44,15 +43,25 @@ public:
is_async_gpu = is_async;
}
+ void OnGpuReady() {
+ gpu_barrier->Sync();
+ }
+
void Initialize();
void Shutdown();
- void Pause(bool paused);
-
- static std::function<void(void*)> GetGuestThreadStartFunc();
- static std::function<void(void*)> GetIdleThreadStartFunc();
- static std::function<void(void*)> GetSuspendThreadStartFunc();
- void* GetStartFuncParamater();
+ std::function<void()> GetGuestActivateFunc() {
+ return [this] { GuestActivate(); };
+ }
+ std::function<void()> GetGuestThreadFunc() {
+ return [this] { GuestThreadFunction(); };
+ }
+ std::function<void()> GetIdleThreadStartFunc() {
+ return [this] { IdleThreadFunction(); };
+ }
+ std::function<void()> GetShutdownThreadStartFunc() {
+ return [this] { ShutdownThreadFunction(); };
+ }
void PreemptSingleCore(bool from_running_enviroment = true);
@@ -61,46 +70,36 @@ public:
}
private:
- static void GuestThreadFunction(void* cpu_manager);
- static void GuestRewindFunction(void* cpu_manager);
- static void IdleThreadFunction(void* cpu_manager);
- static void SuspendThreadFunction(void* cpu_manager);
+ void GuestThreadFunction();
+ void IdleThreadFunction();
+ void ShutdownThreadFunction();
void MultiCoreRunGuestThread();
- void MultiCoreRunGuestLoop();
void MultiCoreRunIdleThread();
- void MultiCoreRunSuspendThread();
- void MultiCorePause(bool paused);
void SingleCoreRunGuestThread();
- void SingleCoreRunGuestLoop();
void SingleCoreRunIdleThread();
- void SingleCoreRunSuspendThread();
- void SingleCorePause(bool paused);
static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core);
- void RunThread(std::stop_token stop_token, std::size_t core);
+ void GuestActivate();
+ void HandleInterrupt();
+ void ShutdownThread();
+ void RunThread(std::size_t core);
struct CoreData {
std::shared_ptr<Common::Fiber> host_context;
- std::unique_ptr<Common::Event> enter_barrier;
- std::unique_ptr<Common::Event> exit_barrier;
- std::atomic<bool> is_running;
- std::atomic<bool> is_paused;
- std::atomic<bool> initialized;
std::jthread host_thread;
};
- std::atomic<bool> running_mode{};
- std::atomic<bool> paused_state{};
-
+ std::unique_ptr<Common::Barrier> gpu_barrier{};
std::array<CoreData, Core::Hardware::NUM_CPU_CORES> core_data{};
bool is_async_gpu{};
bool is_multicore{};
std::atomic<std::size_t> current_core{};
std::size_t idle_count{};
+ std::size_t num_cores{};
static constexpr std::size_t max_cycle_runs = 5;
System& system;
diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp
index 85a666de9..cd7e15a58 100644
--- a/src/core/crypto/aes_util.cpp
+++ b/src/core/crypto/aes_util.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <mbedtls/cipher.h>
diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h
index 230451b8f..a67ba5352 100644
--- a/src/core/crypto/aes_util.h
+++ b/src/core/crypto/aes_util.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp
index 3a2af4f50..b48c3f041 100644
--- a/src/core/crypto/ctr_encryption_layer.cpp
+++ b/src/core/crypto/ctr_encryption_layer.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
diff --git a/src/core/crypto/ctr_encryption_layer.h b/src/core/crypto/ctr_encryption_layer.h
index f86f01b6f..77f08d776 100644
--- a/src/core/crypto/ctr_encryption_layer.h
+++ b/src/core/crypto/ctr_encryption_layer.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/crypto/encryption_layer.cpp b/src/core/crypto/encryption_layer.cpp
index 4c377d7d4..cd10551ec 100644
--- a/src/core/crypto/encryption_layer.cpp
+++ b/src/core/crypto/encryption_layer.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/crypto/encryption_layer.h"
diff --git a/src/core/crypto/encryption_layer.h b/src/core/crypto/encryption_layer.h
index 53619cb38..d3082ba53 100644
--- a/src/core/crypto/encryption_layer.h
+++ b/src/core/crypto/encryption_layer.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index 9244907b5..443323390 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -141,7 +140,6 @@ u64 GetSignatureTypeDataSize(SignatureType type) {
return 0x3C;
}
UNREACHABLE();
- return 0;
}
u64 GetSignatureTypePaddingSize(SignatureType type) {
@@ -156,7 +154,6 @@ u64 GetSignatureTypePaddingSize(SignatureType type) {
return 0x40;
}
UNREACHABLE();
- return 0;
}
SignatureType Ticket::GetSignatureType() const {
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index ac1eb8962..dbf9ebfe4 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp
index d18252a54..97f5c8cea 100644
--- a/src/core/crypto/partition_data_manager.cpp
+++ b/src/core/crypto/partition_data_manager.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
// NOTE TO FUTURE MAINTAINERS:
// When a new version of switch cryptography is released,
diff --git a/src/core/crypto/partition_data_manager.h b/src/core/crypto/partition_data_manager.h
index 7a7b5d038..057a70683 100644
--- a/src/core/crypto/partition_data_manager.h
+++ b/src/core/crypto/partition_data_manager.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/crypto/sha_util.cpp b/src/core/crypto/sha_util.cpp
index 180008a85..7a2c04838 100644
--- a/src/core/crypto/sha_util.cpp
+++ b/src/core/crypto/sha_util.cpp
@@ -1,5 +1,4 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
namespace Crypto {} // namespace Crypto
diff --git a/src/core/crypto/sha_util.h b/src/core/crypto/sha_util.h
index fa3fa9d33..5c2c43dbd 100644
--- a/src/core/crypto/sha_util.h
+++ b/src/core/crypto/sha_util.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/crypto/xts_encryption_layer.cpp b/src/core/crypto/xts_encryption_layer.cpp
index c2b7ea309..b60303412 100644
--- a/src/core/crypto/xts_encryption_layer.cpp
+++ b/src/core/crypto/xts_encryption_layer.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
diff --git a/src/core/crypto/xts_encryption_layer.h b/src/core/crypto/xts_encryption_layer.h
index 5f8f00fe7..735e660cb 100644
--- a/src/core/crypto/xts_encryption_layer.h
+++ b/src/core/crypto/xts_encryption_layer.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
new file mode 100644
index 000000000..339f971e6
--- /dev/null
+++ b/src/core/debugger/debugger.cpp
@@ -0,0 +1,316 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <mutex>
+#include <thread>
+
+#include <boost/asio.hpp>
+#include <boost/process/async_pipe.hpp>
+
+#include "common/logging/log.h"
+#include "common/thread.h"
+#include "core/core.h"
+#include "core/debugger/debugger.h"
+#include "core/debugger/debugger_interface.h"
+#include "core/debugger/gdbstub.h"
+#include "core/hle/kernel/global_scheduler_context.h"
+#include "core/hle/kernel/k_scheduler.h"
+
+template <typename Readable, typename Buffer, typename Callback>
+static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
+ static_assert(std::is_trivial_v<Buffer>);
+ auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))};
+ r.async_read_some(
+ boost_buffer, [&, c](const boost::system::error_code& error, size_t bytes_read) {
+ if (!error.failed()) {
+ const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
+ std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
+ c(received_data);
+ }
+
+ AsyncReceiveInto(r, buffer, c);
+ });
+}
+
+template <typename Readable, typename Buffer>
+static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) {
+ static_assert(std::is_trivial_v<Buffer>);
+ auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))};
+ size_t bytes_read = r.read_some(boost_buffer);
+ const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
+ std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
+ return received_data;
+}
+
+enum class SignalType {
+ Stopped,
+ Watchpoint,
+ ShuttingDown,
+};
+
+struct SignalInfo {
+ SignalType type;
+ Kernel::KThread* thread;
+ const Kernel::DebugWatchpoint* watchpoint;
+};
+
+namespace Core {
+
+class DebuggerImpl : public DebuggerBackend {
+public:
+ explicit DebuggerImpl(Core::System& system_, u16 port)
+ : system{system_}, signal_pipe{io_context}, client_socket{io_context} {
+ frontend = std::make_unique<GDBStub>(*this, system);
+ InitializeServer(port);
+ }
+
+ ~DebuggerImpl() override {
+ ShutdownServer();
+ }
+
+ bool SignalDebugger(SignalInfo signal_info) {
+ {
+ std::scoped_lock lk{connection_lock};
+
+ if (stopped) {
+ // Do not notify the debugger about another event.
+ // It should be ignored.
+ return false;
+ }
+
+ // Set up the state.
+ stopped = true;
+ info = signal_info;
+ }
+
+ // Write a single byte into the pipe to wake up the debug interface.
+ boost::asio::write(signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped)));
+ return true;
+ }
+
+ std::span<const u8> ReadFromClient() override {
+ return ReceiveInto(client_socket, client_data);
+ }
+
+ void WriteToClient(std::span<const u8> data) override {
+ boost::asio::write(client_socket, boost::asio::buffer(data.data(), data.size_bytes()));
+ }
+
+ void SetActiveThread(Kernel::KThread* thread) override {
+ active_thread = thread;
+ }
+
+ Kernel::KThread* GetActiveThread() override {
+ return active_thread;
+ }
+
+private:
+ void InitializeServer(u16 port) {
+ using boost::asio::ip::tcp;
+
+ LOG_INFO(Debug_GDBStub, "Starting server on port {}...", port);
+
+ // Run the connection thread.
+ connection_thread = std::jthread([&, port](std::stop_token stop_token) {
+ try {
+ // Initialize the listening socket and accept a new client.
+ tcp::endpoint endpoint{boost::asio::ip::address_v4::any(), port};
+ tcp::acceptor acceptor{io_context, endpoint};
+
+ acceptor.async_accept(client_socket, [](const auto&) {});
+ io_context.run_one();
+ io_context.restart();
+
+ if (stop_token.stop_requested()) {
+ return;
+ }
+
+ ThreadLoop(stop_token);
+ } catch (const std::exception& ex) {
+ LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what());
+ }
+ });
+ }
+
+ void ShutdownServer() {
+ connection_thread.request_stop();
+ io_context.stop();
+ connection_thread.join();
+ }
+
+ void ThreadLoop(std::stop_token stop_token) {
+ Common::SetCurrentThreadName("Debugger");
+
+ // Set up the client signals for new data.
+ AsyncReceiveInto(signal_pipe, pipe_data, [&](auto d) { PipeData(d); });
+ AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); });
+
+ // Set the active thread.
+ UpdateActiveThread();
+
+ // Set up the frontend.
+ frontend->Connected();
+
+ // Main event loop.
+ while (!stop_token.stop_requested() && io_context.run()) {
+ }
+ }
+
+ void PipeData(std::span<const u8> data) {
+ switch (info.type) {
+ case SignalType::Stopped:
+ case SignalType::Watchpoint:
+ // Stop emulation.
+ PauseEmulation();
+
+ // Notify the client.
+ active_thread = info.thread;
+ UpdateActiveThread();
+
+ if (info.type == SignalType::Watchpoint) {
+ frontend->Watchpoint(active_thread, *info.watchpoint);
+ } else {
+ frontend->Stopped(active_thread);
+ }
+
+ break;
+ case SignalType::ShuttingDown:
+ frontend->ShuttingDown();
+
+ // Wait for emulation to shut down gracefully now.
+ signal_pipe.close();
+ client_socket.shutdown(boost::asio::socket_base::shutdown_both);
+ LOG_INFO(Debug_GDBStub, "Shut down server");
+
+ break;
+ }
+ }
+
+ void ClientData(std::span<const u8> data) {
+ const auto actions{frontend->ClientData(data)};
+ for (const auto action : actions) {
+ switch (action) {
+ case DebuggerAction::Interrupt: {
+ {
+ std::scoped_lock lk{connection_lock};
+ stopped = true;
+ }
+ PauseEmulation();
+ UpdateActiveThread();
+ frontend->Stopped(active_thread);
+ break;
+ }
+ case DebuggerAction::Continue:
+ MarkResumed([&] { ResumeEmulation(); });
+ break;
+ case DebuggerAction::StepThreadUnlocked:
+ MarkResumed([&] {
+ active_thread->SetStepState(Kernel::StepState::StepPending);
+ active_thread->Resume(Kernel::SuspendType::Debug);
+ ResumeEmulation(active_thread);
+ });
+ break;
+ case DebuggerAction::StepThreadLocked: {
+ MarkResumed([&] {
+ active_thread->SetStepState(Kernel::StepState::StepPending);
+ active_thread->Resume(Kernel::SuspendType::Debug);
+ });
+ break;
+ }
+ case DebuggerAction::ShutdownEmulation: {
+ // Spawn another thread that will exit after shutdown,
+ // to avoid a deadlock
+ Core::System* system_ref{&system};
+ std::thread t([system_ref] { system_ref->Exit(); });
+ t.detach();
+ break;
+ }
+ }
+ }
+ }
+
+ void PauseEmulation() {
+ Kernel::KScopedSchedulerLock sl{system.Kernel()};
+
+ // Put all threads to sleep on next scheduler round.
+ for (auto* thread : ThreadList()) {
+ thread->RequestSuspend(Kernel::SuspendType::Debug);
+ }
+ }
+
+ void ResumeEmulation(Kernel::KThread* except = nullptr) {
+ // Wake up all threads.
+ for (auto* thread : ThreadList()) {
+ if (thread == except) {
+ continue;
+ }
+
+ thread->SetStepState(Kernel::StepState::NotStepping);
+ thread->Resume(Kernel::SuspendType::Debug);
+ }
+ }
+
+ template <typename Callback>
+ void MarkResumed(Callback&& cb) {
+ Kernel::KScopedSchedulerLock sl{system.Kernel()};
+ std::scoped_lock cl{connection_lock};
+ stopped = false;
+ cb();
+ }
+
+ void UpdateActiveThread() {
+ const auto& threads{ThreadList()};
+ if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) {
+ active_thread = threads[0];
+ }
+ }
+
+ const std::vector<Kernel::KThread*>& ThreadList() {
+ return system.GlobalSchedulerContext().GetThreadList();
+ }
+
+private:
+ System& system;
+ std::unique_ptr<DebuggerFrontend> frontend;
+
+ std::jthread connection_thread;
+ std::mutex connection_lock;
+ boost::asio::io_context io_context;
+ boost::process::async_pipe signal_pipe;
+ boost::asio::ip::tcp::socket client_socket;
+
+ SignalInfo info;
+ Kernel::KThread* active_thread;
+ bool pipe_data;
+ bool stopped;
+
+ std::array<u8, 4096> client_data;
+};
+
+Debugger::Debugger(Core::System& system, u16 port) {
+ try {
+ impl = std::make_unique<DebuggerImpl>(system, port);
+ } catch (const std::exception& ex) {
+ LOG_CRITICAL(Debug_GDBStub, "Failed to initialize debugger: {}", ex.what());
+ }
+}
+
+Debugger::~Debugger() = default;
+
+bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
+ return impl && impl->SignalDebugger(SignalInfo{SignalType::Stopped, thread, nullptr});
+}
+
+bool Debugger::NotifyThreadWatchpoint(Kernel::KThread* thread,
+ const Kernel::DebugWatchpoint& watch) {
+ return impl && impl->SignalDebugger(SignalInfo{SignalType::Watchpoint, thread, &watch});
+}
+
+void Debugger::NotifyShutdown() {
+ if (impl) {
+ impl->SignalDebugger(SignalInfo{SignalType::ShuttingDown, nullptr, nullptr});
+ }
+}
+
+} // namespace Core
diff --git a/src/core/debugger/debugger.h b/src/core/debugger/debugger.h
new file mode 100644
index 000000000..b2f503376
--- /dev/null
+++ b/src/core/debugger/debugger.h
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_types.h"
+
+namespace Kernel {
+class KThread;
+struct DebugWatchpoint;
+} // namespace Kernel
+
+namespace Core {
+class System;
+
+class DebuggerImpl;
+
+class Debugger {
+public:
+ /**
+ * Blocks and waits for a connection on localhost, port `server_port`.
+ * Does not create the debugger if the port is already in use.
+ */
+ explicit Debugger(Core::System& system, u16 server_port);
+ ~Debugger();
+
+ /**
+ * Notify the debugger that the given thread is stopped
+ * (due to a breakpoint, or due to stopping after a successful step).
+ *
+ * The debugger will asynchronously halt emulation after the notification has
+ * occurred. If another thread attempts to notify before emulation has stopped,
+ * it is ignored and this method will return false. Otherwise it will return true.
+ */
+ bool NotifyThreadStopped(Kernel::KThread* thread);
+
+ /**
+ * Notify the debugger that a shutdown is being performed now and disconnect.
+ */
+ void NotifyShutdown();
+
+ /*
+ * Notify the debugger that the given thread has stopped due to hitting a watchpoint.
+ */
+ bool NotifyThreadWatchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch);
+
+private:
+ std::unique_ptr<DebuggerImpl> impl;
+};
+} // namespace Core
diff --git a/src/core/debugger/debugger_interface.h b/src/core/debugger/debugger_interface.h
new file mode 100644
index 000000000..5b31edc43
--- /dev/null
+++ b/src/core/debugger/debugger_interface.h
@@ -0,0 +1,90 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <functional>
+#include <span>
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Kernel {
+class KThread;
+struct DebugWatchpoint;
+} // namespace Kernel
+
+namespace Core {
+
+enum class DebuggerAction {
+ Interrupt, ///< Stop emulation as soon as possible.
+ Continue, ///< Resume emulation.
+ StepThreadLocked, ///< Step the currently-active thread without resuming others.
+ StepThreadUnlocked, ///< Step the currently-active thread and resume others.
+ ShutdownEmulation, ///< Shut down the emulator.
+};
+
+class DebuggerBackend {
+public:
+ virtual ~DebuggerBackend() = default;
+
+ /**
+ * Can be invoked from a callback to synchronously wait for more data.
+ * Will return as soon as least one byte is received. Reads up to 4096 bytes.
+ */
+ virtual std::span<const u8> ReadFromClient() = 0;
+
+ /**
+ * Can be invoked from a callback to write data to the client.
+ * Returns immediately after the data is sent.
+ */
+ virtual void WriteToClient(std::span<const u8> data) = 0;
+
+ /**
+ * Gets the currently active thread when the debugger is stopped.
+ */
+ virtual Kernel::KThread* GetActiveThread() = 0;
+
+ /**
+ * Sets the currently active thread when the debugger is stopped.
+ */
+ virtual void SetActiveThread(Kernel::KThread* thread) = 0;
+};
+
+class DebuggerFrontend {
+public:
+ explicit DebuggerFrontend(DebuggerBackend& backend_) : backend{backend_} {}
+
+ virtual ~DebuggerFrontend() = default;
+
+ /**
+ * Called after the client has successfully connected to the port.
+ */
+ virtual void Connected() = 0;
+
+ /**
+ * Called when emulation has stopped.
+ */
+ virtual void Stopped(Kernel::KThread* thread) = 0;
+
+ /**
+ * Called when emulation is shutting down.
+ */
+ virtual void ShuttingDown() = 0;
+
+ /*
+ * Called when emulation has stopped on a watchpoint.
+ */
+ virtual void Watchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) = 0;
+
+ /**
+ * Called when new data is asynchronously received on the client socket.
+ * A list of actions to perform is returned.
+ */
+ [[nodiscard]] virtual std::vector<DebuggerAction> ClientData(std::span<const u8> data) = 0;
+
+protected:
+ DebuggerBackend& backend;
+};
+
+} // namespace Core
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
new file mode 100644
index 000000000..884229c77
--- /dev/null
+++ b/src/core/debugger/gdbstub.cpp
@@ -0,0 +1,718 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <atomic>
+#include <numeric>
+#include <optional>
+#include <thread>
+
+#include <boost/algorithm/string.hpp>
+
+#include "common/hex_util.h"
+#include "common/logging/log.h"
+#include "common/scope_exit.h"
+#include "core/arm/arm_interface.h"
+#include "core/core.h"
+#include "core/debugger/gdbstub.h"
+#include "core/debugger/gdbstub_arch.h"
+#include "core/hle/kernel/k_page_table.h"
+#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/loader/loader.h"
+#include "core/memory.h"
+
+namespace Core {
+
+constexpr char GDB_STUB_START = '$';
+constexpr char GDB_STUB_END = '#';
+constexpr char GDB_STUB_ACK = '+';
+constexpr char GDB_STUB_NACK = '-';
+constexpr char GDB_STUB_INT3 = 0x03;
+constexpr int GDB_STUB_SIGTRAP = 5;
+
+constexpr char GDB_STUB_REPLY_ERR[] = "E01";
+constexpr char GDB_STUB_REPLY_OK[] = "OK";
+constexpr char GDB_STUB_REPLY_EMPTY[] = "";
+
+static u8 CalculateChecksum(std::string_view data) {
+ return std::accumulate(data.begin(), data.end(), u8{0},
+ [](u8 lhs, u8 rhs) { return static_cast<u8>(lhs + rhs); });
+}
+
+static std::string EscapeGDB(std::string_view data) {
+ std::string escaped;
+ escaped.reserve(data.size());
+
+ for (char c : data) {
+ switch (c) {
+ case '#':
+ escaped += "}\x03";
+ break;
+ case '$':
+ escaped += "}\x04";
+ break;
+ case '*':
+ escaped += "}\x0a";
+ break;
+ case '}':
+ escaped += "}\x5d";
+ break;
+ default:
+ escaped += c;
+ break;
+ }
+ }
+
+ return escaped;
+}
+
+static std::string EscapeXML(std::string_view data) {
+ std::string escaped;
+ escaped.reserve(data.size());
+
+ for (char c : data) {
+ switch (c) {
+ case '&':
+ escaped += "&amp;";
+ break;
+ case '"':
+ escaped += "&quot;";
+ break;
+ case '<':
+ escaped += "&lt;";
+ break;
+ case '>':
+ escaped += "&gt;";
+ break;
+ default:
+ escaped += c;
+ break;
+ }
+ }
+
+ return escaped;
+}
+
+GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_)
+ : DebuggerFrontend(backend_), system{system_} {
+ if (system.CurrentProcess()->Is64BitProcess()) {
+ arch = std::make_unique<GDBStubA64>();
+ } else {
+ arch = std::make_unique<GDBStubA32>();
+ }
+}
+
+GDBStub::~GDBStub() = default;
+
+void GDBStub::Connected() {}
+
+void GDBStub::ShuttingDown() {}
+
+void GDBStub::Stopped(Kernel::KThread* thread) {
+ SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP));
+}
+
+void GDBStub::Watchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) {
+ const auto status{arch->ThreadStatus(thread, GDB_STUB_SIGTRAP)};
+
+ switch (watch.type) {
+ case Kernel::DebugWatchpointType::Read:
+ SendReply(fmt::format("{}rwatch:{:x};", status, watch.start_address));
+ break;
+ case Kernel::DebugWatchpointType::Write:
+ SendReply(fmt::format("{}watch:{:x};", status, watch.start_address));
+ break;
+ case Kernel::DebugWatchpointType::ReadOrWrite:
+ default:
+ SendReply(fmt::format("{}awatch:{:x};", status, watch.start_address));
+ break;
+ }
+}
+
+std::vector<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) {
+ std::vector<DebuggerAction> actions;
+ current_command.insert(current_command.end(), data.begin(), data.end());
+
+ while (current_command.size() != 0) {
+ ProcessData(actions);
+ }
+
+ return actions;
+}
+
+void GDBStub::ProcessData(std::vector<DebuggerAction>& actions) {
+ const char c{current_command[0]};
+
+ // Acknowledgement
+ if (c == GDB_STUB_ACK || c == GDB_STUB_NACK) {
+ current_command.erase(current_command.begin());
+ return;
+ }
+
+ // Interrupt
+ if (c == GDB_STUB_INT3) {
+ LOG_INFO(Debug_GDBStub, "Received interrupt");
+ current_command.erase(current_command.begin());
+ actions.push_back(DebuggerAction::Interrupt);
+ SendStatus(GDB_STUB_ACK);
+ return;
+ }
+
+ // Otherwise, require the data to be the start of a command
+ if (c != GDB_STUB_START) {
+ LOG_ERROR(Debug_GDBStub, "Invalid command buffer contents: {}", current_command.data());
+ current_command.clear();
+ SendStatus(GDB_STUB_NACK);
+ return;
+ }
+
+ // Continue reading until command is complete
+ while (CommandEnd() == current_command.end()) {
+ const auto new_data{backend.ReadFromClient()};
+ current_command.insert(current_command.end(), new_data.begin(), new_data.end());
+ }
+
+ // Execute and respond to GDB
+ const auto command{DetachCommand()};
+
+ if (command) {
+ SendStatus(GDB_STUB_ACK);
+ ExecuteCommand(*command, actions);
+ } else {
+ SendStatus(GDB_STUB_NACK);
+ }
+}
+
+void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions) {
+ LOG_TRACE(Debug_GDBStub, "Executing command: {}", packet);
+
+ if (packet.length() == 0) {
+ SendReply(GDB_STUB_REPLY_ERR);
+ return;
+ }
+
+ if (packet.starts_with("vCont")) {
+ HandleVCont(packet.substr(5), actions);
+ return;
+ }
+
+ std::string_view command{packet.substr(1, packet.size())};
+
+ switch (packet[0]) {
+ case 'H': {
+ Kernel::KThread* thread{nullptr};
+ s64 thread_id{strtoll(command.data() + 1, nullptr, 16)};
+ if (thread_id >= 1) {
+ thread = GetThreadByID(thread_id);
+ } else {
+ thread = backend.GetActiveThread();
+ }
+
+ if (thread) {
+ SendReply(GDB_STUB_REPLY_OK);
+ backend.SetActiveThread(thread);
+ } else {
+ SendReply(GDB_STUB_REPLY_ERR);
+ }
+ break;
+ }
+ case 'T': {
+ s64 thread_id{strtoll(command.data(), nullptr, 16)};
+ if (GetThreadByID(thread_id)) {
+ SendReply(GDB_STUB_REPLY_OK);
+ } else {
+ SendReply(GDB_STUB_REPLY_ERR);
+ }
+ break;
+ }
+ case 'Q':
+ case 'q':
+ HandleQuery(command);
+ break;
+ case '?':
+ SendReply(arch->ThreadStatus(backend.GetActiveThread(), GDB_STUB_SIGTRAP));
+ break;
+ case 'k':
+ LOG_INFO(Debug_GDBStub, "Shutting down emulation");
+ actions.push_back(DebuggerAction::ShutdownEmulation);
+ break;
+ case 'g':
+ SendReply(arch->ReadRegisters(backend.GetActiveThread()));
+ break;
+ case 'G':
+ arch->WriteRegisters(backend.GetActiveThread(), command);
+ SendReply(GDB_STUB_REPLY_OK);
+ break;
+ case 'p': {
+ const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
+ SendReply(arch->RegRead(backend.GetActiveThread(), reg));
+ break;
+ }
+ case 'P': {
+ const auto sep{std::find(command.begin(), command.end(), '=') - command.begin() + 1};
+ const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
+ arch->RegWrite(backend.GetActiveThread(), reg, std::string_view(command).substr(sep));
+ SendReply(GDB_STUB_REPLY_OK);
+ break;
+ }
+ case 'm': {
+ const auto sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
+ const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
+ const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))};
+
+ if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
+ std::vector<u8> mem(size);
+ system.Memory().ReadBlock(addr, mem.data(), size);
+
+ SendReply(Common::HexToString(mem));
+ } else {
+ SendReply(GDB_STUB_REPLY_ERR);
+ }
+ break;
+ }
+ case 'M': {
+ const auto size_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
+ const auto mem_sep{std::find(command.begin(), command.end(), ':') - command.begin() + 1};
+
+ const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
+ const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
+
+ const auto mem_substr{std::string_view(command).substr(mem_sep)};
+ const auto mem{Common::HexStringToVector(mem_substr, false)};
+
+ if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
+ system.Memory().WriteBlock(addr, mem.data(), size);
+ system.InvalidateCpuInstructionCacheRange(addr, size);
+ SendReply(GDB_STUB_REPLY_OK);
+ } else {
+ SendReply(GDB_STUB_REPLY_ERR);
+ }
+ break;
+ }
+ case 's':
+ actions.push_back(DebuggerAction::StepThreadLocked);
+ break;
+ case 'C':
+ case 'c':
+ actions.push_back(DebuggerAction::Continue);
+ break;
+ case 'Z':
+ HandleBreakpointInsert(command);
+ break;
+ case 'z':
+ HandleBreakpointRemove(command);
+ break;
+ default:
+ SendReply(GDB_STUB_REPLY_EMPTY);
+ break;
+ }
+}
+
+enum class BreakpointType {
+ Software = 0,
+ Hardware = 1,
+ WriteWatch = 2,
+ ReadWatch = 3,
+ AccessWatch = 4,
+};
+
+void GDBStub::HandleBreakpointInsert(std::string_view command) {
+ const auto type{static_cast<BreakpointType>(strtoll(command.data(), nullptr, 16))};
+ const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
+ const auto size_sep{std::find(command.begin() + addr_sep, command.end(), ',') -
+ command.begin() + 1};
+ const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
+ const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
+
+ if (!system.Memory().IsValidVirtualAddressRange(addr, size)) {
+ SendReply(GDB_STUB_REPLY_ERR);
+ return;
+ }
+
+ bool success{};
+
+ switch (type) {
+ case BreakpointType::Software:
+ replaced_instructions[addr] = system.Memory().Read32(addr);
+ system.Memory().Write32(addr, arch->BreakpointInstruction());
+ system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
+ success = true;
+ break;
+ case BreakpointType::WriteWatch:
+ success = system.CurrentProcess()->InsertWatchpoint(system, addr, size,
+ Kernel::DebugWatchpointType::Write);
+ break;
+ case BreakpointType::ReadWatch:
+ success = system.CurrentProcess()->InsertWatchpoint(system, addr, size,
+ Kernel::DebugWatchpointType::Read);
+ break;
+ case BreakpointType::AccessWatch:
+ success = system.CurrentProcess()->InsertWatchpoint(
+ system, addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
+ break;
+ case BreakpointType::Hardware:
+ default:
+ SendReply(GDB_STUB_REPLY_EMPTY);
+ return;
+ }
+
+ if (success) {
+ SendReply(GDB_STUB_REPLY_OK);
+ } else {
+ SendReply(GDB_STUB_REPLY_ERR);
+ }
+}
+
+void GDBStub::HandleBreakpointRemove(std::string_view command) {
+ const auto type{static_cast<BreakpointType>(strtoll(command.data(), nullptr, 16))};
+ const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
+ const auto size_sep{std::find(command.begin() + addr_sep, command.end(), ',') -
+ command.begin() + 1};
+ const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
+ const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
+
+ if (!system.Memory().IsValidVirtualAddressRange(addr, size)) {
+ SendReply(GDB_STUB_REPLY_ERR);
+ return;
+ }
+
+ bool success{};
+
+ switch (type) {
+ case BreakpointType::Software: {
+ const auto orig_insn{replaced_instructions.find(addr)};
+ if (orig_insn != replaced_instructions.end()) {
+ system.Memory().Write32(addr, orig_insn->second);
+ system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
+ replaced_instructions.erase(addr);
+ success = true;
+ }
+ break;
+ }
+ case BreakpointType::WriteWatch:
+ success = system.CurrentProcess()->RemoveWatchpoint(system, addr, size,
+ Kernel::DebugWatchpointType::Write);
+ break;
+ case BreakpointType::ReadWatch:
+ success = system.CurrentProcess()->RemoveWatchpoint(system, addr, size,
+ Kernel::DebugWatchpointType::Read);
+ break;
+ case BreakpointType::AccessWatch:
+ success = system.CurrentProcess()->RemoveWatchpoint(
+ system, addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
+ break;
+ case BreakpointType::Hardware:
+ default:
+ SendReply(GDB_STUB_REPLY_EMPTY);
+ return;
+ }
+
+ if (success) {
+ SendReply(GDB_STUB_REPLY_OK);
+ } else {
+ SendReply(GDB_STUB_REPLY_ERR);
+ }
+}
+
+// Structure offsets are from Atmosphere
+// See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp
+
+static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& memory,
+ const Kernel::KThread* thread) {
+ // Read thread type from TLS
+ const VAddr tls_thread_type{memory.Read32(thread->GetTLSAddress() + 0x1fc)};
+ const VAddr argument_thread_type{thread->GetArgument()};
+
+ if (argument_thread_type && tls_thread_type != argument_thread_type) {
+ // Probably not created by nnsdk, no name available.
+ return std::nullopt;
+ }
+
+ if (!tls_thread_type) {
+ return std::nullopt;
+ }
+
+ const u16 version{memory.Read16(tls_thread_type + 0x26)};
+ VAddr name_pointer{};
+ if (version == 1) {
+ name_pointer = memory.Read32(tls_thread_type + 0xe4);
+ } else {
+ name_pointer = memory.Read32(tls_thread_type + 0xe8);
+ }
+
+ if (!name_pointer) {
+ // No name provided.
+ return std::nullopt;
+ }
+
+ return memory.ReadCString(name_pointer, 256);
+}
+
+static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& memory,
+ const Kernel::KThread* thread) {
+ // Read thread type from TLS
+ const VAddr tls_thread_type{memory.Read64(thread->GetTLSAddress() + 0x1f8)};
+ const VAddr argument_thread_type{thread->GetArgument()};
+
+ if (argument_thread_type && tls_thread_type != argument_thread_type) {
+ // Probably not created by nnsdk, no name available.
+ return std::nullopt;
+ }
+
+ if (!tls_thread_type) {
+ return std::nullopt;
+ }
+
+ const u16 version{memory.Read16(tls_thread_type + 0x46)};
+ VAddr name_pointer{};
+ if (version == 1) {
+ name_pointer = memory.Read64(tls_thread_type + 0x1a0);
+ } else {
+ name_pointer = memory.Read64(tls_thread_type + 0x1a8);
+ }
+
+ if (!name_pointer) {
+ // No name provided.
+ return std::nullopt;
+ }
+
+ return memory.ReadCString(name_pointer, 256);
+}
+
+static std::optional<std::string> GetThreadName(Core::System& system,
+ const Kernel::KThread* thread) {
+ if (system.CurrentProcess()->Is64BitProcess()) {
+ return GetNameFromThreadType64(system.Memory(), thread);
+ } else {
+ return GetNameFromThreadType32(system.Memory(), thread);
+ }
+}
+
+static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) {
+ switch (thread->GetWaitReasonForDebugging()) {
+ case Kernel::ThreadWaitReasonForDebugging::Sleep:
+ return "Sleep";
+ case Kernel::ThreadWaitReasonForDebugging::IPC:
+ return "IPC";
+ case Kernel::ThreadWaitReasonForDebugging::Synchronization:
+ return "Synchronization";
+ case Kernel::ThreadWaitReasonForDebugging::ConditionVar:
+ return "ConditionVar";
+ case Kernel::ThreadWaitReasonForDebugging::Arbitration:
+ return "Arbitration";
+ case Kernel::ThreadWaitReasonForDebugging::Suspended:
+ return "Suspended";
+ default:
+ return "Unknown";
+ }
+}
+
+static std::string GetThreadState(const Kernel::KThread* thread) {
+ switch (thread->GetState()) {
+ case Kernel::ThreadState::Initialized:
+ return "Initialized";
+ case Kernel::ThreadState::Waiting:
+ return fmt::format("Waiting ({})", GetThreadWaitReason(thread));
+ case Kernel::ThreadState::Runnable:
+ return "Runnable";
+ case Kernel::ThreadState::Terminated:
+ return "Terminated";
+ default:
+ return "Unknown";
+ }
+}
+
+static std::string PaginateBuffer(std::string_view buffer, std::string_view request) {
+ const auto amount{request.substr(request.find(',') + 1)};
+ const auto offset_val{static_cast<u64>(strtoll(request.data(), nullptr, 16))};
+ const auto amount_val{static_cast<u64>(strtoll(amount.data(), nullptr, 16))};
+
+ if (offset_val + amount_val > buffer.size()) {
+ return fmt::format("l{}", buffer.substr(offset_val));
+ } else {
+ return fmt::format("m{}", buffer.substr(offset_val, amount_val));
+ }
+}
+
+void GDBStub::HandleQuery(std::string_view command) {
+ if (command.starts_with("TStatus")) {
+ // no tracepoint support
+ SendReply("T0");
+ } else if (command.starts_with("Supported")) {
+ SendReply("PacketSize=4000;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+;"
+ "vContSupported+;QStartNoAckMode+");
+ } else if (command.starts_with("Xfer:features:read:target.xml:")) {
+ const auto target_xml{arch->GetTargetXML()};
+ SendReply(PaginateBuffer(target_xml, command.substr(30)));
+ } else if (command.starts_with("Offsets")) {
+ Loader::AppLoader::Modules modules;
+ system.GetAppLoader().ReadNSOModules(modules);
+
+ const auto main = std::find_if(modules.begin(), modules.end(),
+ [](const auto& key) { return key.second == "main"; });
+ if (main != modules.end()) {
+ SendReply(fmt::format("TextSeg={:x}", main->first));
+ } else {
+ SendReply(fmt::format("TextSeg={:x}",
+ system.CurrentProcess()->PageTable().GetCodeRegionStart()));
+ }
+ } else if (command.starts_with("Xfer:libraries:read::")) {
+ Loader::AppLoader::Modules modules;
+ system.GetAppLoader().ReadNSOModules(modules);
+
+ std::string buffer;
+ buffer += R"(<?xml version="1.0"?>)";
+ buffer += "<library-list>";
+ for (const auto& [base, name] : modules) {
+ buffer += fmt::format(R"(<library name="{}"><segment address="{:#x}"/></library>)",
+ EscapeXML(name), base);
+ }
+ buffer += "</library-list>";
+
+ SendReply(PaginateBuffer(buffer, command.substr(21)));
+ } else if (command.starts_with("fThreadInfo")) {
+ // beginning of list
+ const auto& threads = system.GlobalSchedulerContext().GetThreadList();
+ std::vector<std::string> thread_ids;
+ for (const auto& thread : threads) {
+ thread_ids.push_back(fmt::format("{:x}", thread->GetThreadID()));
+ }
+ SendReply(fmt::format("m{}", fmt::join(thread_ids, ",")));
+ } else if (command.starts_with("sThreadInfo")) {
+ // end of list
+ SendReply("l");
+ } else if (command.starts_with("Xfer:threads:read::")) {
+ std::string buffer;
+ buffer += R"(<?xml version="1.0"?>)";
+ buffer += "<threads>";
+
+ const auto& threads = system.GlobalSchedulerContext().GetThreadList();
+ for (const auto* thread : threads) {
+ auto thread_name{GetThreadName(system, thread)};
+ if (!thread_name) {
+ thread_name = fmt::format("Thread {:d}", thread->GetThreadID());
+ }
+
+ buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="{}">{}</thread>)",
+ thread->GetThreadID(), thread->GetActiveCore(),
+ EscapeXML(*thread_name), GetThreadState(thread));
+ }
+
+ buffer += "</threads>";
+
+ SendReply(PaginateBuffer(buffer, command.substr(19)));
+ } else if (command.starts_with("Attached")) {
+ SendReply("0");
+ } else if (command.starts_with("StartNoAckMode")) {
+ no_ack = true;
+ SendReply(GDB_STUB_REPLY_OK);
+ } else {
+ SendReply(GDB_STUB_REPLY_EMPTY);
+ }
+}
+
+void GDBStub::HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions) {
+ if (command == "?") {
+ // Continuing and stepping are supported
+ // (signal is ignored, but required for GDB to use vCont)
+ SendReply("vCont;c;C;s;S");
+ return;
+ }
+
+ Kernel::KThread* stepped_thread{nullptr};
+ bool lock_execution{true};
+
+ std::vector<std::string> entries;
+ boost::split(entries, command.substr(1), boost::is_any_of(";"));
+ for (const auto& thread_action : entries) {
+ std::vector<std::string> parts;
+ boost::split(parts, thread_action, boost::is_any_of(":"));
+
+ if (parts.size() == 1 && (parts[0] == "c" || parts[0].starts_with("C"))) {
+ lock_execution = false;
+ }
+ if (parts.size() == 2 && (parts[0] == "s" || parts[0].starts_with("S"))) {
+ stepped_thread = GetThreadByID(strtoll(parts[1].data(), nullptr, 16));
+ }
+ }
+
+ if (stepped_thread) {
+ backend.SetActiveThread(stepped_thread);
+ actions.push_back(lock_execution ? DebuggerAction::StepThreadLocked
+ : DebuggerAction::StepThreadUnlocked);
+ } else {
+ actions.push_back(DebuggerAction::Continue);
+ }
+}
+
+Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
+ const auto& threads{system.GlobalSchedulerContext().GetThreadList()};
+ for (auto* thread : threads) {
+ if (thread->GetThreadID() == thread_id) {
+ return thread;
+ }
+ }
+
+ return nullptr;
+}
+
+std::vector<char>::const_iterator GDBStub::CommandEnd() const {
+ // Find the end marker
+ const auto end{std::find(current_command.begin(), current_command.end(), GDB_STUB_END)};
+
+ // Require the checksum to be present
+ return std::min(end + 2, current_command.end());
+}
+
+std::optional<std::string> GDBStub::DetachCommand() {
+ // Slice the string part from the beginning to the end marker
+ const auto end{CommandEnd()};
+
+ // Extract possible command data
+ std::string data(current_command.data(), end - current_command.begin() + 1);
+
+ // Shift over the remaining contents
+ current_command.erase(current_command.begin(), end + 1);
+
+ // Validate received command
+ if (data[0] != GDB_STUB_START) {
+ LOG_ERROR(Debug_GDBStub, "Invalid start data: {}", data[0]);
+ return std::nullopt;
+ }
+
+ u8 calculated = CalculateChecksum(std::string_view(data).substr(1, data.size() - 4));
+ u8 received = static_cast<u8>(strtoll(data.data() + data.size() - 2, nullptr, 16));
+
+ // Verify checksum
+ if (calculated != received) {
+ LOG_ERROR(Debug_GDBStub, "Checksum mismatch: calculated {:02x}, received {:02x}",
+ calculated, received);
+ return std::nullopt;
+ }
+
+ return data.substr(1, data.size() - 4);
+}
+
+void GDBStub::SendReply(std::string_view data) {
+ const auto escaped{EscapeGDB(data)};
+ const auto output{fmt::format("{}{}{}{:02x}", GDB_STUB_START, escaped, GDB_STUB_END,
+ CalculateChecksum(escaped))};
+ LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output);
+
+ // C++ string support is complete rubbish
+ const u8* output_begin = reinterpret_cast<const u8*>(output.data());
+ const u8* output_end = output_begin + output.size();
+ backend.WriteToClient(std::span<const u8>(output_begin, output_end));
+}
+
+void GDBStub::SendStatus(char status) {
+ if (no_ack) {
+ return;
+ }
+
+ std::array<u8, 1> buf = {static_cast<u8>(status)};
+ LOG_TRACE(Debug_GDBStub, "Writing status: {}", status);
+ backend.WriteToClient(buf);
+}
+
+} // namespace Core
diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h
new file mode 100644
index 000000000..0b0f56e4b
--- /dev/null
+++ b/src/core/debugger/gdbstub.h
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <optional>
+#include <string_view>
+#include <vector>
+
+#include "core/debugger/debugger_interface.h"
+#include "core/debugger/gdbstub_arch.h"
+
+namespace Core {
+
+class System;
+
+class GDBStub : public DebuggerFrontend {
+public:
+ explicit GDBStub(DebuggerBackend& backend, Core::System& system);
+ ~GDBStub() override;
+
+ void Connected() override;
+ void Stopped(Kernel::KThread* thread) override;
+ void ShuttingDown() override;
+ void Watchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) override;
+ std::vector<DebuggerAction> ClientData(std::span<const u8> data) override;
+
+private:
+ void ProcessData(std::vector<DebuggerAction>& actions);
+ void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions);
+ void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions);
+ void HandleQuery(std::string_view command);
+ void HandleBreakpointInsert(std::string_view command);
+ void HandleBreakpointRemove(std::string_view command);
+ std::vector<char>::const_iterator CommandEnd() const;
+ std::optional<std::string> DetachCommand();
+ Kernel::KThread* GetThreadByID(u64 thread_id);
+
+ void SendReply(std::string_view data);
+ void SendStatus(char status);
+
+private:
+ Core::System& system;
+ std::unique_ptr<GDBStubArch> arch;
+ std::vector<char> current_command;
+ std::map<VAddr, u32> replaced_instructions;
+ bool no_ack{};
+};
+
+} // namespace Core
diff --git a/src/core/debugger/gdbstub_arch.cpp b/src/core/debugger/gdbstub_arch.cpp
new file mode 100644
index 000000000..4bef09bd7
--- /dev/null
+++ b/src/core/debugger/gdbstub_arch.cpp
@@ -0,0 +1,487 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/hex_util.h"
+#include "core/debugger/gdbstub_arch.h"
+#include "core/hle/kernel/k_thread.h"
+
+namespace Core {
+
+template <typename T>
+static T HexToValue(std::string_view hex) {
+ static_assert(std::is_trivially_copyable_v<T>);
+ T value{};
+ const auto mem{Common::HexStringToVector(hex, false)};
+ std::memcpy(&value, mem.data(), std::min(mem.size(), sizeof(T)));
+ return value;
+}
+
+template <typename T>
+static std::string ValueToHex(const T value) {
+ static_assert(std::is_trivially_copyable_v<T>);
+ std::array<u8, sizeof(T)> mem{};
+ std::memcpy(mem.data(), &value, sizeof(T));
+ return Common::HexToString(mem);
+}
+
+template <typename T>
+static T GetSIMDRegister(const std::array<u32, 64>& simd_regs, size_t offset) {
+ static_assert(std::is_trivially_copyable_v<T>);
+ T value{};
+ std::memcpy(&value, reinterpret_cast<const u8*>(simd_regs.data()) + sizeof(T) * offset,
+ sizeof(T));
+ return value;
+}
+
+template <typename T>
+static void PutSIMDRegister(std::array<u32, 64>& simd_regs, size_t offset, const T value) {
+ static_assert(std::is_trivially_copyable_v<T>);
+ std::memcpy(reinterpret_cast<u8*>(simd_regs.data()) + sizeof(T) * offset, &value, sizeof(T));
+}
+
+// For sample XML files see the GDB source /gdb/features
+// This XML defines what the registers are for this specific ARM device
+std::string GDBStubA64::GetTargetXML() const {
+ constexpr const char* target_xml =
+ R"(<?xml version="1.0"?>
+<!DOCTYPE target SYSTEM "gdb-target.dtd">
+<target version="1.0">
+ <architecture>aarch64</architecture>
+ <feature name="org.gnu.gdb.aarch64.core">
+ <reg name="x0" bitsize="64"/>
+ <reg name="x1" bitsize="64"/>
+ <reg name="x2" bitsize="64"/>
+ <reg name="x3" bitsize="64"/>
+ <reg name="x4" bitsize="64"/>
+ <reg name="x5" bitsize="64"/>
+ <reg name="x6" bitsize="64"/>
+ <reg name="x7" bitsize="64"/>
+ <reg name="x8" bitsize="64"/>
+ <reg name="x9" bitsize="64"/>
+ <reg name="x10" bitsize="64"/>
+ <reg name="x11" bitsize="64"/>
+ <reg name="x12" bitsize="64"/>
+ <reg name="x13" bitsize="64"/>
+ <reg name="x14" bitsize="64"/>
+ <reg name="x15" bitsize="64"/>
+ <reg name="x16" bitsize="64"/>
+ <reg name="x17" bitsize="64"/>
+ <reg name="x18" bitsize="64"/>
+ <reg name="x19" bitsize="64"/>
+ <reg name="x20" bitsize="64"/>
+ <reg name="x21" bitsize="64"/>
+ <reg name="x22" bitsize="64"/>
+ <reg name="x23" bitsize="64"/>
+ <reg name="x24" bitsize="64"/>
+ <reg name="x25" bitsize="64"/>
+ <reg name="x26" bitsize="64"/>
+ <reg name="x27" bitsize="64"/>
+ <reg name="x28" bitsize="64"/>
+ <reg name="x29" bitsize="64"/>
+ <reg name="x30" bitsize="64"/>
+ <reg name="sp" bitsize="64" type="data_ptr"/>
+ <reg name="pc" bitsize="64" type="code_ptr"/>
+ <flags id="cpsr_flags" size="4">
+ <field name="SP" start="0" end="0"/>
+ <field name="" start="1" end="1"/>
+ <field name="EL" start="2" end="3"/>
+ <field name="nRW" start="4" end="4"/>
+ <field name="" start="5" end="5"/>
+ <field name="F" start="6" end="6"/>
+ <field name="I" start="7" end="7"/>
+ <field name="A" start="8" end="8"/>
+ <field name="D" start="9" end="9"/>
+ <field name="IL" start="20" end="20"/>
+ <field name="SS" start="21" end="21"/>
+ <field name="V" start="28" end="28"/>
+ <field name="C" start="29" end="29"/>
+ <field name="Z" start="30" end="30"/>
+ <field name="N" start="31" end="31"/>
+ </flags>
+ <reg name="cpsr" bitsize="32" type="cpsr_flags"/>
+ </feature>
+ <feature name="org.gnu.gdb.aarch64.fpu">
+ <vector id="v2d" type="ieee_double" count="2"/>
+ <vector id="v2u" type="uint64" count="2"/>
+ <vector id="v2i" type="int64" count="2"/>
+ <vector id="v4f" type="ieee_single" count="4"/>
+ <vector id="v4u" type="uint32" count="4"/>
+ <vector id="v4i" type="int32" count="4"/>
+ <vector id="v8u" type="uint16" count="8"/>
+ <vector id="v8i" type="int16" count="8"/>
+ <vector id="v16u" type="uint8" count="16"/>
+ <vector id="v16i" type="int8" count="16"/>
+ <vector id="v1u" type="uint128" count="1"/>
+ <vector id="v1i" type="int128" count="1"/>
+ <union id="vnd">
+ <field name="f" type="v2d"/>
+ <field name="u" type="v2u"/>
+ <field name="s" type="v2i"/>
+ </union>
+ <union id="vns">
+ <field name="f" type="v4f"/>
+ <field name="u" type="v4u"/>
+ <field name="s" type="v4i"/>
+ </union>
+ <union id="vnh">
+ <field name="u" type="v8u"/>
+ <field name="s" type="v8i"/>
+ </union>
+ <union id="vnb">
+ <field name="u" type="v16u"/>
+ <field name="s" type="v16i"/>
+ </union>
+ <union id="vnq">
+ <field name="u" type="v1u"/>
+ <field name="s" type="v1i"/>
+ </union>
+ <union id="aarch64v">
+ <field name="d" type="vnd"/>
+ <field name="s" type="vns"/>
+ <field name="h" type="vnh"/>
+ <field name="b" type="vnb"/>
+ <field name="q" type="vnq"/>
+ </union>
+ <reg name="v0" bitsize="128" type="aarch64v" regnum="34"/>
+ <reg name="v1" bitsize="128" type="aarch64v" />
+ <reg name="v2" bitsize="128" type="aarch64v" />
+ <reg name="v3" bitsize="128" type="aarch64v" />
+ <reg name="v4" bitsize="128" type="aarch64v" />
+ <reg name="v5" bitsize="128" type="aarch64v" />
+ <reg name="v6" bitsize="128" type="aarch64v" />
+ <reg name="v7" bitsize="128" type="aarch64v" />
+ <reg name="v8" bitsize="128" type="aarch64v" />
+ <reg name="v9" bitsize="128" type="aarch64v" />
+ <reg name="v10" bitsize="128" type="aarch64v"/>
+ <reg name="v11" bitsize="128" type="aarch64v"/>
+ <reg name="v12" bitsize="128" type="aarch64v"/>
+ <reg name="v13" bitsize="128" type="aarch64v"/>
+ <reg name="v14" bitsize="128" type="aarch64v"/>
+ <reg name="v15" bitsize="128" type="aarch64v"/>
+ <reg name="v16" bitsize="128" type="aarch64v"/>
+ <reg name="v17" bitsize="128" type="aarch64v"/>
+ <reg name="v18" bitsize="128" type="aarch64v"/>
+ <reg name="v19" bitsize="128" type="aarch64v"/>
+ <reg name="v20" bitsize="128" type="aarch64v"/>
+ <reg name="v21" bitsize="128" type="aarch64v"/>
+ <reg name="v22" bitsize="128" type="aarch64v"/>
+ <reg name="v23" bitsize="128" type="aarch64v"/>
+ <reg name="v24" bitsize="128" type="aarch64v"/>
+ <reg name="v25" bitsize="128" type="aarch64v"/>
+ <reg name="v26" bitsize="128" type="aarch64v"/>
+ <reg name="v27" bitsize="128" type="aarch64v"/>
+ <reg name="v28" bitsize="128" type="aarch64v"/>
+ <reg name="v29" bitsize="128" type="aarch64v"/>
+ <reg name="v30" bitsize="128" type="aarch64v"/>
+ <reg name="v31" bitsize="128" type="aarch64v"/>
+ <reg name="fpsr" bitsize="32"/>
+ <reg name="fpcr" bitsize="32"/>
+ </feature>
+</target>)";
+
+ return target_xml;
+}
+
+std::string GDBStubA64::RegRead(const Kernel::KThread* thread, size_t id) const {
+ if (!thread) {
+ return "";
+ }
+
+ const auto& context{thread->GetContext64()};
+ const auto& gprs{context.cpu_registers};
+ const auto& fprs{context.vector_registers};
+
+ if (id < SP_REGISTER) {
+ return ValueToHex(gprs[id]);
+ } else if (id == SP_REGISTER) {
+ return ValueToHex(context.sp);
+ } else if (id == PC_REGISTER) {
+ return ValueToHex(context.pc);
+ } else if (id == PSTATE_REGISTER) {
+ return ValueToHex(context.pstate);
+ } else if (id >= Q0_REGISTER && id < FPSR_REGISTER) {
+ return ValueToHex(fprs[id - Q0_REGISTER]);
+ } else if (id == FPSR_REGISTER) {
+ return ValueToHex(context.fpsr);
+ } else if (id == FPCR_REGISTER) {
+ return ValueToHex(context.fpcr);
+ } else {
+ return "";
+ }
+}
+
+void GDBStubA64::RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const {
+ if (!thread) {
+ return;
+ }
+
+ auto& context{thread->GetContext64()};
+
+ if (id < SP_REGISTER) {
+ context.cpu_registers[id] = HexToValue<u64>(value);
+ } else if (id == SP_REGISTER) {
+ context.sp = HexToValue<u64>(value);
+ } else if (id == PC_REGISTER) {
+ context.pc = HexToValue<u64>(value);
+ } else if (id == PSTATE_REGISTER) {
+ context.pstate = HexToValue<u32>(value);
+ } else if (id >= Q0_REGISTER && id < FPSR_REGISTER) {
+ context.vector_registers[id - Q0_REGISTER] = HexToValue<u128>(value);
+ } else if (id == FPSR_REGISTER) {
+ context.fpsr = HexToValue<u32>(value);
+ } else if (id == FPCR_REGISTER) {
+ context.fpcr = HexToValue<u32>(value);
+ }
+}
+
+std::string GDBStubA64::ReadRegisters(const Kernel::KThread* thread) const {
+ std::string output;
+
+ for (size_t reg = 0; reg <= FPCR_REGISTER; reg++) {
+ output += RegRead(thread, reg);
+ }
+
+ return output;
+}
+
+void GDBStubA64::WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const {
+ for (size_t i = 0, reg = 0; reg <= FPCR_REGISTER; reg++) {
+ if (reg <= SP_REGISTER || reg == PC_REGISTER) {
+ RegWrite(thread, reg, register_data.substr(i, 16));
+ i += 16;
+ } else if (reg == PSTATE_REGISTER || reg == FPCR_REGISTER || reg == FPSR_REGISTER) {
+ RegWrite(thread, reg, register_data.substr(i, 8));
+ i += 8;
+ } else if (reg >= Q0_REGISTER && reg < FPCR_REGISTER) {
+ RegWrite(thread, reg, register_data.substr(i, 32));
+ i += 32;
+ }
+ }
+}
+
+std::string GDBStubA64::ThreadStatus(const Kernel::KThread* thread, u8 signal) const {
+ return fmt::format("T{:02x}{:02x}:{};{:02x}:{};{:02x}:{};thread:{:x};", signal, PC_REGISTER,
+ RegRead(thread, PC_REGISTER), SP_REGISTER, RegRead(thread, SP_REGISTER),
+ LR_REGISTER, RegRead(thread, LR_REGISTER), thread->GetThreadID());
+}
+
+u32 GDBStubA64::BreakpointInstruction() const {
+ // A64: brk #0
+ return 0xd4200000;
+}
+
+std::string GDBStubA32::GetTargetXML() const {
+ constexpr const char* target_xml =
+ R"(<?xml version="1.0"?>
+<!DOCTYPE target SYSTEM "gdb-target.dtd">
+<target version="1.0">
+ <architecture>arm</architecture>
+ <feature name="org.gnu.gdb.arm.core">
+ <reg name="r0" bitsize="32" type="uint32"/>
+ <reg name="r1" bitsize="32" type="uint32"/>
+ <reg name="r2" bitsize="32" type="uint32"/>
+ <reg name="r3" bitsize="32" type="uint32"/>
+ <reg name="r4" bitsize="32" type="uint32"/>
+ <reg name="r5" bitsize="32" type="uint32"/>
+ <reg name="r6" bitsize="32" type="uint32"/>
+ <reg name="r7" bitsize="32" type="uint32"/>
+ <reg name="r8" bitsize="32" type="uint32"/>
+ <reg name="r9" bitsize="32" type="uint32"/>
+ <reg name="r10" bitsize="32" type="uint32"/>
+ <reg name="r11" bitsize="32" type="uint32"/>
+ <reg name="r12" bitsize="32" type="uint32"/>
+ <reg name="sp" bitsize="32" type="data_ptr"/>
+ <reg name="lr" bitsize="32" type="code_ptr"/>
+ <reg name="pc" bitsize="32" type="code_ptr"/>
+ <!-- The CPSR is register 25, rather than register 16, because
+ the FPA registers historically were placed between the PC
+ and the CPSR in the "g" packet. -->
+ <reg name="cpsr" bitsize="32" regnum="25"/>
+ </feature>
+ <feature name="org.gnu.gdb.arm.vfp">
+ <vector id="neon_uint8x8" type="uint8" count="8"/>
+ <vector id="neon_uint16x4" type="uint16" count="4"/>
+ <vector id="neon_uint32x2" type="uint32" count="2"/>
+ <vector id="neon_float32x2" type="ieee_single" count="2"/>
+ <union id="neon_d">
+ <field name="u8" type="neon_uint8x8"/>
+ <field name="u16" type="neon_uint16x4"/>
+ <field name="u32" type="neon_uint32x2"/>
+ <field name="u64" type="uint64"/>
+ <field name="f32" type="neon_float32x2"/>
+ <field name="f64" type="ieee_double"/>
+ </union>
+ <vector id="neon_uint8x16" type="uint8" count="16"/>
+ <vector id="neon_uint16x8" type="uint16" count="8"/>
+ <vector id="neon_uint32x4" type="uint32" count="4"/>
+ <vector id="neon_uint64x2" type="uint64" count="2"/>
+ <vector id="neon_float32x4" type="ieee_single" count="4"/>
+ <vector id="neon_float64x2" type="ieee_double" count="2"/>
+ <union id="neon_q">
+ <field name="u8" type="neon_uint8x16"/>
+ <field name="u16" type="neon_uint16x8"/>
+ <field name="u32" type="neon_uint32x4"/>
+ <field name="u64" type="neon_uint64x2"/>
+ <field name="f32" type="neon_float32x4"/>
+ <field name="f64" type="neon_float64x2"/>
+ </union>
+ <reg name="d0" bitsize="64" type="neon_d" regnum="32"/>
+ <reg name="d1" bitsize="64" type="neon_d"/>
+ <reg name="d2" bitsize="64" type="neon_d"/>
+ <reg name="d3" bitsize="64" type="neon_d"/>
+ <reg name="d4" bitsize="64" type="neon_d"/>
+ <reg name="d5" bitsize="64" type="neon_d"/>
+ <reg name="d6" bitsize="64" type="neon_d"/>
+ <reg name="d7" bitsize="64" type="neon_d"/>
+ <reg name="d8" bitsize="64" type="neon_d"/>
+ <reg name="d9" bitsize="64" type="neon_d"/>
+ <reg name="d10" bitsize="64" type="neon_d"/>
+ <reg name="d11" bitsize="64" type="neon_d"/>
+ <reg name="d12" bitsize="64" type="neon_d"/>
+ <reg name="d13" bitsize="64" type="neon_d"/>
+ <reg name="d14" bitsize="64" type="neon_d"/>
+ <reg name="d15" bitsize="64" type="neon_d"/>
+ <reg name="d16" bitsize="64" type="neon_d"/>
+ <reg name="d17" bitsize="64" type="neon_d"/>
+ <reg name="d18" bitsize="64" type="neon_d"/>
+ <reg name="d19" bitsize="64" type="neon_d"/>
+ <reg name="d20" bitsize="64" type="neon_d"/>
+ <reg name="d21" bitsize="64" type="neon_d"/>
+ <reg name="d22" bitsize="64" type="neon_d"/>
+ <reg name="d23" bitsize="64" type="neon_d"/>
+ <reg name="d24" bitsize="64" type="neon_d"/>
+ <reg name="d25" bitsize="64" type="neon_d"/>
+ <reg name="d26" bitsize="64" type="neon_d"/>
+ <reg name="d27" bitsize="64" type="neon_d"/>
+ <reg name="d28" bitsize="64" type="neon_d"/>
+ <reg name="d29" bitsize="64" type="neon_d"/>
+ <reg name="d30" bitsize="64" type="neon_d"/>
+ <reg name="d31" bitsize="64" type="neon_d"/>
+
+ <reg name="q0" bitsize="128" type="neon_q" regnum="64"/>
+ <reg name="q1" bitsize="128" type="neon_q"/>
+ <reg name="q2" bitsize="128" type="neon_q"/>
+ <reg name="q3" bitsize="128" type="neon_q"/>
+ <reg name="q4" bitsize="128" type="neon_q"/>
+ <reg name="q5" bitsize="128" type="neon_q"/>
+ <reg name="q6" bitsize="128" type="neon_q"/>
+ <reg name="q7" bitsize="128" type="neon_q"/>
+ <reg name="q8" bitsize="128" type="neon_q"/>
+ <reg name="q9" bitsize="128" type="neon_q"/>
+ <reg name="q10" bitsize="128" type="neon_q"/>
+ <reg name="q10" bitsize="128" type="neon_q"/>
+ <reg name="q12" bitsize="128" type="neon_q"/>
+ <reg name="q13" bitsize="128" type="neon_q"/>
+ <reg name="q14" bitsize="128" type="neon_q"/>
+ <reg name="q15" bitsize="128" type="neon_q"/>
+
+ <reg name="fpscr" bitsize="32" type="int" group="float" regnum="80"/>
+ </feature>
+</target>)";
+
+ return target_xml;
+}
+
+std::string GDBStubA32::RegRead(const Kernel::KThread* thread, size_t id) const {
+ if (!thread) {
+ return "";
+ }
+
+ const auto& context{thread->GetContext32()};
+ const auto& gprs{context.cpu_registers};
+ const auto& fprs{context.extension_registers};
+
+ if (id <= PC_REGISTER) {
+ return ValueToHex(gprs[id]);
+ } else if (id == CPSR_REGISTER) {
+ return ValueToHex(context.cpsr);
+ } else if (id >= D0_REGISTER && id < Q0_REGISTER) {
+ const u64 dN{GetSIMDRegister<u64>(fprs, id - D0_REGISTER)};
+ return ValueToHex(dN);
+ } else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) {
+ const u128 qN{GetSIMDRegister<u128>(fprs, id - Q0_REGISTER)};
+ return ValueToHex(qN);
+ } else if (id == FPSCR_REGISTER) {
+ return ValueToHex(context.fpscr);
+ } else {
+ return "";
+ }
+}
+
+void GDBStubA32::RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const {
+ if (!thread) {
+ return;
+ }
+
+ auto& context{thread->GetContext32()};
+ auto& fprs{context.extension_registers};
+
+ if (id <= PC_REGISTER) {
+ context.cpu_registers[id] = HexToValue<u32>(value);
+ } else if (id == CPSR_REGISTER) {
+ context.cpsr = HexToValue<u32>(value);
+ } else if (id >= D0_REGISTER && id < Q0_REGISTER) {
+ PutSIMDRegister(fprs, id - D0_REGISTER, HexToValue<u64>(value));
+ } else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) {
+ PutSIMDRegister(fprs, id - Q0_REGISTER, HexToValue<u128>(value));
+ } else if (id == FPSCR_REGISTER) {
+ context.fpscr = HexToValue<u32>(value);
+ }
+}
+
+std::string GDBStubA32::ReadRegisters(const Kernel::KThread* thread) const {
+ std::string output;
+
+ for (size_t reg = 0; reg <= FPSCR_REGISTER; reg++) {
+ const bool gpr{reg <= PC_REGISTER};
+ const bool dfpr{reg >= D0_REGISTER && reg < Q0_REGISTER};
+ const bool qfpr{reg >= Q0_REGISTER && reg < FPSCR_REGISTER};
+
+ if (!(gpr || dfpr || qfpr || reg == CPSR_REGISTER || reg == FPSCR_REGISTER)) {
+ continue;
+ }
+
+ output += RegRead(thread, reg);
+ }
+
+ return output;
+}
+
+void GDBStubA32::WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const {
+ for (size_t i = 0, reg = 0; reg <= FPSCR_REGISTER; reg++) {
+ const bool gpr{reg <= PC_REGISTER};
+ const bool dfpr{reg >= D0_REGISTER && reg < Q0_REGISTER};
+ const bool qfpr{reg >= Q0_REGISTER && reg < FPSCR_REGISTER};
+
+ if (gpr || reg == CPSR_REGISTER || reg == FPSCR_REGISTER) {
+ RegWrite(thread, reg, register_data.substr(i, 8));
+ i += 8;
+ } else if (dfpr) {
+ RegWrite(thread, reg, register_data.substr(i, 16));
+ i += 16;
+ } else if (qfpr) {
+ RegWrite(thread, reg, register_data.substr(i, 32));
+ i += 32;
+ }
+
+ if (reg == PC_REGISTER) {
+ reg = CPSR_REGISTER - 1;
+ } else if (reg == CPSR_REGISTER) {
+ reg = D0_REGISTER - 1;
+ }
+ }
+}
+
+std::string GDBStubA32::ThreadStatus(const Kernel::KThread* thread, u8 signal) const {
+ return fmt::format("T{:02x}{:02x}:{};{:02x}:{};{:02x}:{};thread:{:x};", signal, PC_REGISTER,
+ RegRead(thread, PC_REGISTER), SP_REGISTER, RegRead(thread, SP_REGISTER),
+ LR_REGISTER, RegRead(thread, LR_REGISTER), thread->GetThreadID());
+}
+
+u32 GDBStubA32::BreakpointInstruction() const {
+ // A32: trap
+ // T32: trap + b #4
+ return 0xe7ffdefe;
+}
+
+} // namespace Core
diff --git a/src/core/debugger/gdbstub_arch.h b/src/core/debugger/gdbstub_arch.h
new file mode 100644
index 000000000..2540d6456
--- /dev/null
+++ b/src/core/debugger/gdbstub_arch.h
@@ -0,0 +1,68 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "common/common_types.h"
+
+namespace Kernel {
+class KThread;
+}
+
+namespace Core {
+
+class GDBStubArch {
+public:
+ virtual ~GDBStubArch() = default;
+ virtual std::string GetTargetXML() const = 0;
+ virtual std::string RegRead(const Kernel::KThread* thread, size_t id) const = 0;
+ virtual void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const = 0;
+ virtual std::string ReadRegisters(const Kernel::KThread* thread) const = 0;
+ virtual void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const = 0;
+ virtual std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const = 0;
+ virtual u32 BreakpointInstruction() const = 0;
+};
+
+class GDBStubA64 final : public GDBStubArch {
+public:
+ std::string GetTargetXML() const override;
+ std::string RegRead(const Kernel::KThread* thread, size_t id) const override;
+ void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const override;
+ std::string ReadRegisters(const Kernel::KThread* thread) const override;
+ void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const override;
+ std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const override;
+ u32 BreakpointInstruction() const override;
+
+private:
+ static constexpr u32 LR_REGISTER = 30;
+ static constexpr u32 SP_REGISTER = 31;
+ static constexpr u32 PC_REGISTER = 32;
+ static constexpr u32 PSTATE_REGISTER = 33;
+ static constexpr u32 Q0_REGISTER = 34;
+ static constexpr u32 FPSR_REGISTER = 66;
+ static constexpr u32 FPCR_REGISTER = 67;
+};
+
+class GDBStubA32 final : public GDBStubArch {
+public:
+ std::string GetTargetXML() const override;
+ std::string RegRead(const Kernel::KThread* thread, size_t id) const override;
+ void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const override;
+ std::string ReadRegisters(const Kernel::KThread* thread) const override;
+ void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const override;
+ std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const override;
+ u32 BreakpointInstruction() const override;
+
+private:
+ static constexpr u32 SP_REGISTER = 13;
+ static constexpr u32 LR_REGISTER = 14;
+ static constexpr u32 PC_REGISTER = 15;
+ static constexpr u32 CPSR_REGISTER = 25;
+ static constexpr u32 D0_REGISTER = 32;
+ static constexpr u32 Q0_REGISTER = 64;
+ static constexpr u32 FPSCR_REGISTER = 80;
+};
+
+} // namespace Core
diff --git a/src/core/device_memory.cpp b/src/core/device_memory.cpp
index f19c0515f..f8b5be2b4 100644
--- a/src/core/device_memory.cpp
+++ b/src/core/device_memory.cpp
@@ -1,12 +1,14 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/device_memory.h"
+#include "hle/kernel/board/nintendo/nx/k_system_control.h"
namespace Core {
-DeviceMemory::DeviceMemory() : buffer{DramMemoryMap::Size, 1ULL << 39} {}
+DeviceMemory::DeviceMemory()
+ : buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
+ 1ULL << 39} {}
DeviceMemory::~DeviceMemory() = default;
} // namespace Core
diff --git a/src/core/device_memory.h b/src/core/device_memory.h
index c4d17705f..df61b0c0b 100644
--- a/src/core/device_memory.h
+++ b/src/core/device_memory.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -12,12 +11,8 @@ namespace Core {
namespace DramMemoryMap {
enum : u64 {
Base = 0x80000000ULL,
- Size = 0x100000000ULL,
- End = Base + Size,
KernelReserveBase = Base + 0x60000,
SlabHeapBase = KernelReserveBase + 0x85000,
- SlapHeapSize = 0xa21000,
- SlabHeapEnd = SlabHeapBase + SlapHeapSize,
};
}; // namespace DramMemoryMap
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index f3891acf1..c750c0da7 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/format.h>
#include "common/fs/path_util.h"
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index 136485881..26f0c6e5e 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index c6300be59..f23d9373b 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <string>
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index 0fd9fa87c..1283f8216 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/common_funcs.h b/src/core/file_sys/common_funcs.h
index 7ed97aa50..3dd8f1244 100644
--- a/src/core/file_sys/common_funcs.h
+++ b/src/core/file_sys/common_funcs.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 7019a7a68..78e56bbbd 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
@@ -420,7 +419,7 @@ std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type
Core::Crypto::Mode::ECB);
cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt);
- Core::Crypto::Key128 out;
+ Core::Crypto::Key128 out{};
if (type == NCASectionCryptoType::XTS) {
std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
} else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) {
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index e9eccdea3..7fdc45ea7 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index 05936f3c3..be25da2f6 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/string_util.h"
#include "common/swap.h"
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 3e0b45630..75295519c 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h
index 21c7aefc8..a853c00f3 100644
--- a/src/core/file_sys/directory.h
+++ b/src/core/file_sys/directory.h
@@ -1,11 +1,9 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstddef>
-#include <iterator>
#include "common/common_funcs.h"
#include "common/common_types.h"
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index 1a920b45d..7cee0c7df 100644
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,14 +7,14 @@
namespace FileSys {
-constexpr ResultCode ERROR_PATH_NOT_FOUND{ErrorModule::FS, 1};
-constexpr ResultCode ERROR_PATH_ALREADY_EXISTS{ErrorModule::FS, 2};
-constexpr ResultCode ERROR_ENTITY_NOT_FOUND{ErrorModule::FS, 1002};
-constexpr ResultCode ERROR_SD_CARD_NOT_FOUND{ErrorModule::FS, 2001};
-constexpr ResultCode ERROR_OUT_OF_BOUNDS{ErrorModule::FS, 3005};
-constexpr ResultCode ERROR_FAILED_MOUNT_ARCHIVE{ErrorModule::FS, 3223};
-constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001};
-constexpr ResultCode ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
-constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
+constexpr Result ERROR_PATH_NOT_FOUND{ErrorModule::FS, 1};
+constexpr Result ERROR_PATH_ALREADY_EXISTS{ErrorModule::FS, 2};
+constexpr Result ERROR_ENTITY_NOT_FOUND{ErrorModule::FS, 1002};
+constexpr Result ERROR_SD_CARD_NOT_FOUND{ErrorModule::FS, 2001};
+constexpr Result ERROR_OUT_OF_BOUNDS{ErrorModule::FS, 3005};
+constexpr Result ERROR_FAILED_MOUNT_ARCHIVE{ErrorModule::FS, 3223};
+constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001};
+constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
+constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
} // namespace FileSys
diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp
index 1ca1d536f..1ff83c08c 100644
--- a/src/core/file_sys/fsmitm_romfsbuild.cpp
+++ b/src/core/file_sys/fsmitm_romfsbuild.cpp
@@ -1,26 +1,5 @@
-/*
- * Copyright (c) 2018 Atmosphère-NX
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms and conditions of the GNU General Public License,
- * version 2, as published by the Free Software Foundation.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/*
- * Adapted by DarkLordZach for use/interaction with yuzu
- *
- * Modifications Copyright 2018 yuzu emulator team
- * Licensed under GPLv2 or any later version
- * Refer to the license.txt file included.
- */
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <string_view>
diff --git a/src/core/file_sys/fsmitm_romfsbuild.h b/src/core/file_sys/fsmitm_romfsbuild.h
index 8d4d89fab..06e5d5a47 100644
--- a/src/core/file_sys/fsmitm_romfsbuild.h
+++ b/src/core/file_sys/fsmitm_romfsbuild.h
@@ -1,26 +1,5 @@
-/*
- * Copyright (c) 2018 Atmosphère-NX
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms and conditions of the GNU General Public License,
- * version 2, as published by the Free Software Foundation.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/*
- * Adapted by DarkLordZach for use/interaction with yuzu
- *
- * Modifications Copyright 2018 yuzu emulator team
- * Licensed under GPLv2 or any later version
- * Refer to the license.txt file included.
- */
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index a6101f1c0..5aab428bb 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
@@ -218,9 +217,7 @@ void IPSwitchCompiler::Parse() {
break;
} else if (StartsWith(line, "@nsobid-")) {
// NSO Build ID Specifier
- auto raw_build_id = line.substr(8);
- if (raw_build_id.size() != 0x40)
- raw_build_id.resize(0x40, '0');
+ const auto raw_build_id = fmt::format("{:0<64}", line.substr(8));
nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
} else if (StartsWith(line, "#")) {
// Mandatory Comment
@@ -288,7 +285,8 @@ void IPSwitchCompiler::Parse() {
std::copy(value.begin(), value.end(), std::back_inserter(replace));
} else {
// hex replacement
- const auto value = patch_line.substr(9);
+ const auto value =
+ patch_line.substr(9, patch_line.find_first_of(" /\r\n", 9) - 9);
replace = Common::HexStringToVector(value, is_little_endian);
}
diff --git a/src/core/file_sys/ips_layer.h b/src/core/file_sys/ips_layer.h
index 450b2f71e..f2717bae7 100644
--- a/src/core/file_sys/ips_layer.h
+++ b/src/core/file_sys/ips_layer.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/kernel_executable.cpp b/src/core/file_sys/kernel_executable.cpp
index ef93ef3ed..70c062f4c 100644
--- a/src/core/file_sys/kernel_executable.cpp
+++ b/src/core/file_sys/kernel_executable.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h
index 79ca82f8b..d5b9199b5 100644
--- a/src/core/file_sys/kernel_executable.h
+++ b/src/core/file_sys/kernel_executable.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/mode.h b/src/core/file_sys/mode.h
index 6c49a64e2..9596ef4fd 100644
--- a/src/core/file_sys/mode.h
+++ b/src/core/file_sys/mode.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp
index f5cb4aa8c..52c78020c 100644
--- a/src/core/file_sys/nca_metadata.cpp
+++ b/src/core/file_sys/nca_metadata.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/common_types.h"
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h
index 75c74ae28..c59ece010 100644
--- a/src/core/file_sys/nca_metadata.h
+++ b/src/core/file_sys/nca_metadata.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp
index b36827b75..2735d053b 100644
--- a/src/core/file_sys/nca_patch.cpp
+++ b/src/core/file_sys/nca_patch.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -51,7 +50,7 @@ std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockTyp
low = mid + 1;
}
}
- UNREACHABLE_MSG("Offset could not be found in BKTR block.");
+ ASSERT_MSG(false, "Offset could not be found in BKTR block.");
return {0, 0};
}
} // Anonymous namespace
diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h
index 503cf473e..595e3ef09 100644
--- a/src/core/file_sys/nca_patch.h
+++ b/src/core/file_sys/nca_patch.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index c5967049e..2527ae606 100644
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstddef>
diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h
index 0f831148e..b6e3a2b0c 100644
--- a/src/core/file_sys/partition_filesystem.h
+++ b/src/core/file_sys/partition_filesystem.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 7c0950bb0..4c80e13a9 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -10,7 +9,10 @@
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
+#ifndef _WIN32
#include "common/string_util.h"
+#endif
+
#include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
@@ -128,15 +130,6 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
if (exefs == nullptr)
return exefs;
- if (Settings::values.dump_exefs) {
- LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id);
- const auto dump_dir = fs_controller.GetModificationDumpRoot(title_id);
- if (dump_dir != nullptr) {
- const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs");
- VfsRawCopyD(exefs, exefs_dir);
- }
- }
-
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
@@ -154,28 +147,41 @@ 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) {
+ LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id);
+ const auto dump_dir = fs_controller.GetModificationDumpRoot(title_id);
+ if (dump_dir != nullptr) {
+ const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs");
+ VfsRawCopyD(exefs, exefs_dir);
}
}
@@ -185,6 +191,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) const {
const auto& disabled = Settings::values.disabled_addons[title_id];
+ const auto nso_build_id = fmt::format("{:0<64}", build_id);
std::vector<VirtualFile> out;
out.reserve(patch_dirs.size());
@@ -197,21 +204,18 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() == "ips") {
auto name = file->GetName();
- const auto p1 = name.substr(0, name.find('.'));
- const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
- if (build_id == this_build_id)
+ const auto this_build_id =
+ fmt::format("{:0<64}", name.substr(0, name.find('.')));
+ if (nso_build_id == this_build_id)
out.push_back(file);
} else if (file->GetExtension() == "pchtxt") {
IPSwitchCompiler compiler{file};
if (!compiler.IsValid())
continue;
- auto this_build_id = Common::HexToString(compiler.GetBuildID());
- this_build_id =
- this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1);
-
- if (build_id == this_build_id)
+ const auto this_build_id = Common::HexToString(compiler.GetBuildID());
+ if (nso_build_id == this_build_id)
out.push_back(file);
}
}
@@ -533,11 +537,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/patch_manager.h b/src/core/file_sys/patch_manager.h
index 3be871f35..69d15e2f8 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index 484d4baea..08d489eab 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstddef>
#include <vector>
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index c89a1c445..2e8960b07 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -1,12 +1,13 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <vector>
+
#include "common/bit_field.h"
+#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/vfs_types.h"
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 7a646b5f1..878d832c2 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <random>
@@ -109,7 +108,7 @@ ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
return ContentRecordType::HtmlDocument;
default:
- UNREACHABLE_MSG("Invalid NCAContentType={:02X}", type);
+ ASSERT_MSG(false, "Invalid NCAContentType={:02X}", type);
return ContentRecordType{};
}
}
@@ -387,15 +386,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/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index d042aef90..587f8cae8 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp
index 120032134..ddcfe5980 100644
--- a/src/core/file_sys/romfs.cpp
+++ b/src/core/file_sys/romfs.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h
index 82e683782..5d7f0c2a8 100644
--- a/src/core/file_sys/romfs.h
+++ b/src/core/file_sys/romfs.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 291b746b6..ae7a3511b 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include "common/assert.h"
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index 2c93a49a5..14936031f 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index e6f8514c9..8c1b2523c 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include "common/assert.h"
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index de415b0c4..a763b94c8 100644
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index c0e13e56f..1df022c9e 100644
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include "core/file_sys/registered_cache.h"
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index 3a3d11f3a..3aebfb25e 100644
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index f03124e3d..c90e6e372 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index 030f36c09..3226b884a 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/system_archive/data/font_chinese_simplified.cpp b/src/core/file_sys/system_archive/data/font_chinese_simplified.cpp
index a676867e5..a1daac3a9 100644
--- a/src/core/file_sys/system_archive/data/font_chinese_simplified.cpp
+++ b/src/core/file_sys/system_archive/data/font_chinese_simplified.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/system_archive/data/font_chinese_simplified.h"
diff --git a/src/core/file_sys/system_archive/data/font_chinese_simplified.h b/src/core/file_sys/system_archive/data/font_chinese_simplified.h
index 161de4542..33932c456 100644
--- a/src/core/file_sys/system_archive/data/font_chinese_simplified.h
+++ b/src/core/file_sys/system_archive/data/font_chinese_simplified.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/system_archive/data/font_chinese_traditional.cpp b/src/core/file_sys/system_archive/data/font_chinese_traditional.cpp
index cc70a4032..9b92ec0ff 100644
--- a/src/core/file_sys/system_archive/data/font_chinese_traditional.cpp
+++ b/src/core/file_sys/system_archive/data/font_chinese_traditional.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/system_archive/data/font_chinese_traditional.h"
diff --git a/src/core/file_sys/system_archive/data/font_chinese_traditional.h b/src/core/file_sys/system_archive/data/font_chinese_traditional.h
index b16c8e550..c2ebd4d96 100644
--- a/src/core/file_sys/system_archive/data/font_chinese_traditional.h
+++ b/src/core/file_sys/system_archive/data/font_chinese_traditional.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/system_archive/data/font_extended_chinese_simplified.cpp b/src/core/file_sys/system_archive/data/font_extended_chinese_simplified.cpp
index 18b8c87e7..9e43ef2c1 100644
--- a/src/core/file_sys/system_archive/data/font_extended_chinese_simplified.cpp
+++ b/src/core/file_sys/system_archive/data/font_extended_chinese_simplified.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/system_archive/data/font_extended_chinese_simplified.h"
diff --git a/src/core/file_sys/system_archive/data/font_extended_chinese_simplified.h b/src/core/file_sys/system_archive/data/font_extended_chinese_simplified.h
index de8ac57ac..d640cf755 100644
--- a/src/core/file_sys/system_archive/data/font_extended_chinese_simplified.h
+++ b/src/core/file_sys/system_archive/data/font_extended_chinese_simplified.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/system_archive/data/font_korean.cpp b/src/core/file_sys/system_archive/data/font_korean.cpp
index ae9228958..cba7fe0f8 100644
--- a/src/core/file_sys/system_archive/data/font_korean.cpp
+++ b/src/core/file_sys/system_archive/data/font_korean.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/system_archive/data/font_korean.h"
diff --git a/src/core/file_sys/system_archive/data/font_korean.h b/src/core/file_sys/system_archive/data/font_korean.h
index e1b02c4e5..1d9ab4b1f 100644
--- a/src/core/file_sys/system_archive/data/font_korean.h
+++ b/src/core/file_sys/system_archive/data/font_korean.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp b/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp
index 29ef110a6..eaa8ec254 100644
--- a/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp
+++ b/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/system_archive/data/font_nintendo_extended.h"
diff --git a/src/core/file_sys/system_archive/data/font_nintendo_extended.h b/src/core/file_sys/system_archive/data/font_nintendo_extended.h
index edb9df914..247ebb5af 100644
--- a/src/core/file_sys/system_archive/data/font_nintendo_extended.h
+++ b/src/core/file_sys/system_archive/data/font_nintendo_extended.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/system_archive/data/font_standard.cpp b/src/core/file_sys/system_archive/data/font_standard.cpp
index 8f4d8448e..b71ba8a83 100644
--- a/src/core/file_sys/system_archive/data/font_standard.cpp
+++ b/src/core/file_sys/system_archive/data/font_standard.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/system_archive/data/font_standard.h"
diff --git a/src/core/file_sys/system_archive/data/font_standard.h b/src/core/file_sys/system_archive/data/font_standard.h
index 757593c4b..ab44108d6 100644
--- a/src/core/file_sys/system_archive/data/font_standard.h
+++ b/src/core/file_sys/system_archive/data/font_standard.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/system_archive/mii_model.cpp b/src/core/file_sys/system_archive/mii_model.cpp
index d65c7d234..5c87b42f8 100644
--- a/src/core/file_sys/system_archive/mii_model.cpp
+++ b/src/core/file_sys/system_archive/mii_model.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/system_archive/mii_model.h"
#include "core/file_sys/vfs_vector.h"
diff --git a/src/core/file_sys/system_archive/mii_model.h b/src/core/file_sys/system_archive/mii_model.h
index 6c2d9398b..b6cbefe24 100644
--- a/src/core/file_sys/system_archive/mii_model.h
+++ b/src/core/file_sys/system_archive/mii_model.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/system_archive/ng_word.cpp b/src/core/file_sys/system_archive/ng_word.cpp
index 8d86d563a..5cf6749da 100644
--- a/src/core/file_sys/system_archive/ng_word.cpp
+++ b/src/core/file_sys/system_archive/ng_word.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/format.h>
#include "common/common_types.h"
diff --git a/src/core/file_sys/system_archive/ng_word.h b/src/core/file_sys/system_archive/ng_word.h
index cd81e0abb..1d7b49532 100644
--- a/src/core/file_sys/system_archive/ng_word.h
+++ b/src/core/file_sys/system_archive/ng_word.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/system_archive/shared_font.cpp b/src/core/file_sys/system_archive/shared_font.cpp
index c5cdf7d9b..3210583f0 100644
--- a/src/core/file_sys/system_archive/shared_font.cpp
+++ b/src/core/file_sys/system_archive/shared_font.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/system_archive/data/font_chinese_simplified.h"
#include "core/file_sys/system_archive/data/font_chinese_traditional.h"
@@ -10,7 +9,7 @@
#include "core/file_sys/system_archive/data/font_standard.h"
#include "core/file_sys/system_archive/shared_font.h"
#include "core/file_sys/vfs_vector.h"
-#include "core/hle/service/ns/pl_u.h"
+#include "core/hle/service/ns/iplatform_service_manager.h"
namespace FileSys::SystemArchive {
diff --git a/src/core/file_sys/system_archive/shared_font.h b/src/core/file_sys/system_archive/shared_font.h
index 6d8de565b..d1cd1dc44 100644
--- a/src/core/file_sys/system_archive/shared_font.h
+++ b/src/core/file_sys/system_archive/shared_font.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp
index a6696024e..6abac793b 100644
--- a/src/core/file_sys/system_archive/system_archive.cpp
+++ b/src/core/file_sys/system_archive/system_archive.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/file_sys/romfs.h"
diff --git a/src/core/file_sys/system_archive/system_archive.h b/src/core/file_sys/system_archive/system_archive.h
index 724a8eb17..02d9157bb 100644
--- a/src/core/file_sys/system_archive/system_archive.h
+++ b/src/core/file_sys/system_archive/system_archive.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp
index 9b76d007e..bd493ecca 100644
--- a/src/core/file_sys/system_archive/system_version.cpp
+++ b/src/core/file_sys/system_archive/system_version.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/system_archive/system_version.h"
#include "core/file_sys/vfs_vector.h"
diff --git a/src/core/file_sys/system_archive/system_version.h b/src/core/file_sys/system_archive/system_version.h
index deed79b26..21b5514a9 100644
--- a/src/core/file_sys/system_archive/system_version.h
+++ b/src/core/file_sys/system_archive/system_version.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/system_archive/time_zone_binary.cpp b/src/core/file_sys/system_archive/time_zone_binary.cpp
index 8fd005012..85383998d 100644
--- a/src/core/file_sys/system_archive/time_zone_binary.cpp
+++ b/src/core/file_sys/system_archive/time_zone_binary.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <vector>
diff --git a/src/core/file_sys/system_archive/time_zone_binary.h b/src/core/file_sys/system_archive/time_zone_binary.h
index 266c23537..d0e1a4acd 100644
--- a/src/core/file_sys/system_archive/time_zone_binary.h
+++ b/src/core/file_sys/system_archive/time_zone_binary.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index f5ad10b15..0f6618b31 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <numeric>
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
index 1b9365853..8fc1738a4 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp
index 5f8c09124..d23623aa0 100644
--- a/src/core/file_sys/vfs_concat.cpp
+++ b/src/core/file_sys/vfs_concat.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <utility>
diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h
index bd091451e..9be0261b6 100644
--- a/src/core/file_sys/vfs_concat.h
+++ b/src/core/file_sys/vfs_concat.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp
index e093c4db2..da05dd395 100644
--- a/src/core/file_sys/vfs_layered.cpp
+++ b/src/core/file_sys/vfs_layered.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <utility>
diff --git a/src/core/file_sys/vfs_layered.h b/src/core/file_sys/vfs_layered.h
index cd6baf28c..a62112e9d 100644
--- a/src/core/file_sys/vfs_layered.h
+++ b/src/core/file_sys/vfs_layered.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp
index 870ed1cf8..d950a6633 100644
--- a/src/core/file_sys/vfs_offset.cpp
+++ b/src/core/file_sys/vfs_offset.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <utility>
diff --git a/src/core/file_sys/vfs_offset.h b/src/core/file_sys/vfs_offset.h
index 7ce1eb336..6c051ca00 100644
--- a/src/core/file_sys/vfs_offset.h
+++ b/src/core/file_sys/vfs_offset.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index f4073b76a..cc0076238 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstddef>
@@ -145,7 +144,7 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_
LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path);
}
} else {
- UNREACHABLE();
+ ASSERT(false);
return nullptr;
}
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
index 746e624cb..acde1ac89 100644
--- a/src/core/file_sys/vfs_real.h
+++ b/src/core/file_sys/vfs_real.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h
index f5b66cf71..ca3f989ef 100644
--- a/src/core/file_sys/vfs_static.h
+++ b/src/core/file_sys/vfs_static.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/vfs_types.h b/src/core/file_sys/vfs_types.h
index ed0724717..4a583ed64 100644
--- a/src/core/file_sys/vfs_types.h
+++ b/src/core/file_sys/vfs_types.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp
index f64b88639..251d9d7c9 100644
--- a/src/core/file_sys/vfs_vector.cpp
+++ b/src/core/file_sys/vfs_vector.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <utility>
diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h
index 73f180070..bfedb6e42 100644
--- a/src/core/file_sys/vfs_vector.h
+++ b/src/core/file_sys/vfs_vector.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp
index d6fe1af47..ede0aa11a 100644
--- a/src/core/file_sys/xts_archive.cpp
+++ b/src/core/file_sys/xts_archive.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h
index 63a032b68..abbe5f716 100644
--- a/src/core/file_sys/xts_archive.h
+++ b/src/core/file_sys/xts_archive.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp
index e1033b634..6c230f619 100644
--- a/src/core/frontend/applets/controller.cpp
+++ b/src/core/frontend/applets/controller.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/logging/log.h"
@@ -66,7 +65,7 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
controller->Connect(true);
} else {
- UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!");
+ ASSERT_MSG(false, "Unable to add a new controller based on the given parameters!");
}
}
diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h
index 014bc8901..1d2850ad5 100644
--- a/src/core/frontend/applets/controller.h
+++ b/src/core/frontend/applets/controller.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/frontend/applets/error.cpp b/src/core/frontend/applets/error.cpp
index dceb20ff8..f8b961098 100644
--- a/src/core/frontend/applets/error.cpp
+++ b/src/core/frontend/applets/error.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/frontend/applets/error.h"
@@ -9,12 +8,12 @@ namespace Core::Frontend {
ErrorApplet::~ErrorApplet() = default;
-void DefaultErrorApplet::ShowError(ResultCode error, std::function<void()> finished) const {
+void DefaultErrorApplet::ShowError(Result error, std::function<void()> finished) const {
LOG_CRITICAL(Service_Fatal, "Application requested error display: {:04}-{:04} (raw={:08X})",
error.module.Value(), error.description.Value(), error.raw);
}
-void DefaultErrorApplet::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
+void DefaultErrorApplet::ShowErrorWithTimestamp(Result error, std::chrono::seconds time,
std::function<void()> finished) const {
LOG_CRITICAL(
Service_Fatal,
@@ -22,7 +21,7 @@ void DefaultErrorApplet::ShowErrorWithTimestamp(ResultCode error, std::chrono::s
error.module.Value(), error.description.Value(), error.raw, time.count());
}
-void DefaultErrorApplet::ShowCustomErrorText(ResultCode error, std::string main_text,
+void DefaultErrorApplet::ShowCustomErrorText(Result error, std::string main_text,
std::string detail_text,
std::function<void()> finished) const {
LOG_CRITICAL(Service_Fatal,
diff --git a/src/core/frontend/applets/error.h b/src/core/frontend/applets/error.h
index 699df940d..f378f8805 100644
--- a/src/core/frontend/applets/error.h
+++ b/src/core/frontend/applets/error.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -15,22 +14,22 @@ class ErrorApplet {
public:
virtual ~ErrorApplet();
- virtual void ShowError(ResultCode error, std::function<void()> finished) const = 0;
+ virtual void ShowError(Result error, std::function<void()> finished) const = 0;
- virtual void ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
+ virtual void ShowErrorWithTimestamp(Result error, std::chrono::seconds time,
std::function<void()> finished) const = 0;
- virtual void ShowCustomErrorText(ResultCode error, std::string dialog_text,
+ virtual void ShowCustomErrorText(Result error, std::string dialog_text,
std::string fullscreen_text,
std::function<void()> finished) const = 0;
};
class DefaultErrorApplet final : public ErrorApplet {
public:
- void ShowError(ResultCode error, std::function<void()> finished) const override;
- void ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
+ void ShowError(Result error, std::function<void()> finished) const override;
+ void ShowErrorWithTimestamp(Result error, std::chrono::seconds time,
std::function<void()> finished) const override;
- void ShowCustomErrorText(ResultCode error, std::string main_text, std::string detail_text,
+ void ShowCustomErrorText(Result error, std::string main_text, std::string detail_text,
std::function<void()> finished) const override;
};
diff --git a/src/core/frontend/applets/general_frontend.cpp b/src/core/frontend/applets/general_frontend.cpp
index 7483ffb76..29a00fb6f 100644
--- a/src/core/frontend/applets/general_frontend.cpp
+++ b/src/core/frontend/applets/general_frontend.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/frontend/applets/general_frontend.h"
diff --git a/src/core/frontend/applets/general_frontend.h b/src/core/frontend/applets/general_frontend.h
index 1647aa975..cbec8b4ad 100644
--- a/src/core/frontend/applets/general_frontend.h
+++ b/src/core/frontend/applets/general_frontend.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/frontend/applets/mii_edit.cpp b/src/core/frontend/applets/mii_edit.cpp
new file mode 100644
index 000000000..d37b5368a
--- /dev/null
+++ b/src/core/frontend/applets/mii_edit.cpp
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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..58fa2039b
--- /dev/null
+++ b/src/core/frontend/applets/mii_edit.h
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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/frontend/applets/profile_select.cpp b/src/core/frontend/applets/profile_select.cpp
index 4c58c310f..d11fbce0a 100644
--- a/src/core/frontend/applets/profile_select.cpp
+++ b/src/core/frontend/applets/profile_select.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h"
#include "core/frontend/applets/profile_select.h"
diff --git a/src/core/frontend/applets/profile_select.h b/src/core/frontend/applets/profile_select.h
index 3506b9885..8d6ee5279 100644
--- a/src/core/frontend/applets/profile_select.h
+++ b/src/core/frontend/applets/profile_select.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/frontend/applets/software_keyboard.cpp b/src/core/frontend/applets/software_keyboard.cpp
index c4863ee73..020c7fa5e 100644
--- a/src/core/frontend/applets/software_keyboard.cpp
+++ b/src/core/frontend/applets/software_keyboard.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
diff --git a/src/core/frontend/applets/software_keyboard.h b/src/core/frontend/applets/software_keyboard.h
index 490c55cc2..094d1e713 100644
--- a/src/core/frontend/applets/software_keyboard.h
+++ b/src/core/frontend/applets/software_keyboard.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -18,6 +17,8 @@ struct KeyboardInitializeParameters {
std::u16string sub_text;
std::u16string guide_text;
std::u16string initial_text;
+ char16_t left_optional_symbol_key;
+ char16_t right_optional_symbol_key;
u32 max_text_length;
u32 min_text_length;
s32 initial_cursor_position;
diff --git a/src/core/frontend/applets/web_browser.cpp b/src/core/frontend/applets/web_browser.cpp
index be4736f47..27c7086be 100644
--- a/src/core/frontend/applets/web_browser.cpp
+++ b/src/core/frontend/applets/web_browser.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/frontend/applets/web_browser.h"
diff --git a/src/core/frontend/applets/web_browser.h b/src/core/frontend/applets/web_browser.h
index b6a60c994..1411274f8 100644
--- a/src/core/frontend/applets/web_browser.h
+++ b/src/core/frontend/applets/web_browser.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index 57c6ffc43..1be2dccb0 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include "core/frontend/emu_window.h"
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index e413a520a..ac1906d5e 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -42,11 +41,20 @@ public:
context.MakeCurrent();
}
~Scoped() {
- context.DoneCurrent();
+ if (active) {
+ context.DoneCurrent();
+ }
+ }
+
+ /// In the event that context was destroyed before the Scoped is destroyed, this provides a
+ /// mechanism to prevent calling a destroyed object's method during the deconstructor
+ void Cancel() {
+ active = false;
}
private:
GraphicsContext& context;
+ bool active{true};
};
/// Calls MakeCurrent on the context and calls DoneCurrent when the scope for the returned value
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index 26a5b12aa..90dd68ff1 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cmath>
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index 8e341e4e2..1561d994e 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hardware_interrupt_manager.cpp b/src/core/hardware_interrupt_manager.cpp
deleted file mode 100644
index 290db505e..000000000
--- a/src/core/hardware_interrupt_manager.cpp
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2019 Yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "core/core.h"
-#include "core/core_timing.h"
-#include "core/hardware_interrupt_manager.h"
-#include "core/hle/service/nvdrv/nvdrv_interface.h"
-#include "core/hle/service/sm/sm.h"
-
-namespace Core::Hardware {
-
-InterruptManager::InterruptManager(Core::System& system_in) : system(system_in) {
- gpu_interrupt_event = Core::Timing::CreateEvent(
- "GPUInterrupt", [this](std::uintptr_t message, std::chrono::nanoseconds) {
- auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv");
- const u32 syncpt = static_cast<u32>(message >> 32);
- const u32 value = static_cast<u32>(message);
- nvdrv->SignalGPUInterruptSyncpt(syncpt, value);
- });
-}
-
-InterruptManager::~InterruptManager() = default;
-
-void InterruptManager::GPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
- const u64 msg = (static_cast<u64>(syncpoint_id) << 32ULL) | value;
- system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{10}, gpu_interrupt_event, msg);
-}
-
-} // namespace Core::Hardware
diff --git a/src/core/hardware_interrupt_manager.h b/src/core/hardware_interrupt_manager.h
deleted file mode 100644
index 5fa306ae0..000000000
--- a/src/core/hardware_interrupt_manager.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2019 Yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-
-#include "common/common_types.h"
-
-namespace Core {
-class System;
-}
-
-namespace Core::Timing {
-struct EventType;
-}
-
-namespace Core::Hardware {
-
-class InterruptManager {
-public:
- explicit InterruptManager(Core::System& system);
- ~InterruptManager();
-
- void GPUInterruptSyncpt(u32 syncpoint_id, u32 value);
-
-private:
- Core::System& system;
- std::shared_ptr<Core::Timing::EventType> gpu_interrupt_event;
-};
-
-} // namespace Core::Hardware
diff --git a/src/core/hardware_properties.h b/src/core/hardware_properties.h
index 176a72c67..13cbdb734 100644
--- a/src/core/hardware_properties.h
+++ b/src/core/hardware_properties.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -26,6 +25,9 @@ constexpr std::array<s32, Common::BitSize<u64>()> VirtualToPhysicalCoreMap{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
};
+// Cortex-A57 supports 4 memory watchpoints
+constexpr u64 NUM_WATCHPOINTS = 4;
+
} // namespace Hardware
} // namespace Core
diff --git a/src/core/hid/emulated_console.cpp b/src/core/hid/emulated_console.cpp
index eef0ff493..aac45907d 100644
--- a/src/core/hid/emulated_console.cpp
+++ b/src/core/hid/emulated_console.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h"
#include "core/hid/emulated_console.h"
@@ -28,12 +27,19 @@ void EmulatedConsole::SetTouchParams() {
// We can't use mouse as touch if native mouse is enabled
touch_params[index++] = Common::ParamPackage{"engine:mouse,axis_x:10,axis_y:11,button:0"};
}
- touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:0,axis_y:1,button:0"};
- touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:2,axis_y:3,button:1"};
+
+ touch_params[index++] =
+ Common::ParamPackage{"engine:touch,axis_x:0,axis_y:1,button:0,touch_id:0"};
+ touch_params[index++] =
+ Common::ParamPackage{"engine:touch,axis_x:2,axis_y:3,button:1,touch_id:1"};
+ touch_params[index++] =
+ Common::ParamPackage{"engine:touch,axis_x:4,axis_y:5,button:2,touch_id:2"};
+ touch_params[index++] =
+ Common::ParamPackage{"engine:touch,axis_x:6,axis_y:7,button:3,touch_id:3"};
touch_params[index++] =
- Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536"};
+ Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536,touch_id:0"};
touch_params[index++] =
- Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072"};
+ Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072,touch_id:1"};
const auto button_index =
static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
@@ -132,7 +138,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 +157,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 +173,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 +181,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 +198,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 +233,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..1c510cd19 100644
--- a/src/core/hid/emulated_console.h
+++ b/src/core/hid/emulated_console.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -183,6 +182,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 2bee173b3..025f1c78e 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -1,7 +1,7 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/thread.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/input_converter.h"
@@ -85,23 +85,26 @@ void EmulatedController::ReloadFromSettings() {
motion_params[index] = Common::ParamPackage(player.motions[index]);
}
+ controller.colors_state.fullkey = {
+ .body = GetNpadColor(player.body_color_left),
+ .button = GetNpadColor(player.button_color_left),
+ };
controller.colors_state.left = {
- .body = player.body_color_left,
- .button = player.button_color_left,
+ .body = GetNpadColor(player.body_color_left),
+ .button = GetNpadColor(player.button_color_left),
};
-
controller.colors_state.right = {
- .body = player.body_color_right,
- .button = player.button_color_right,
+ .body = GetNpadColor(player.body_color_right),
+ .button = GetNpadColor(player.button_color_right),
};
- controller.colors_state.fullkey = controller.colors_state.left;
-
// Other or debug controller should always be a pro controller
if (npad_id_type != NpadIdType::Other) {
SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
+ original_npad_type = npad_type;
} else {
SetNpadStyleIndex(NpadStyleIndex::ProController);
+ original_npad_type = npad_type;
}
if (player.connected) {
@@ -127,10 +130,17 @@ void EmulatedController::LoadDevices() {
battery_params[LeftIndex].Set("battery", true);
battery_params[RightIndex].Set("battery", true);
+ camera_params = Common::ParamPackage{"engine:camera,camera:1"};
+ nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
+
output_params[LeftIndex] = left_joycon;
output_params[RightIndex] = right_joycon;
+ output_params[2] = camera_params;
+ output_params[3] = nfc_params;
output_params[LeftIndex].Set("output", true);
output_params[RightIndex].Set("output", true);
+ output_params[2].Set("output", true);
+ output_params[3].Set("output", true);
LoadTASParams();
@@ -147,6 +157,8 @@ void EmulatedController::LoadDevices() {
Common::Input::CreateDevice<Common::Input::InputDevice>);
std::transform(battery_params.begin(), battery_params.end(), battery_devices.begin(),
Common::Input::CreateDevice<Common::Input::InputDevice>);
+ camera_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(camera_params);
+ nfc_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(nfc_params);
std::transform(output_params.begin(), output_params.end(), output_devices.begin(),
Common::Input::CreateDevice<Common::Input::OutputDevice>);
@@ -268,6 +280,24 @@ void EmulatedController::ReloadInput() {
motion_devices[index]->ForceUpdate();
}
+ if (camera_devices) {
+ camera_devices->SetCallback({
+ .on_change =
+ [this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); },
+ });
+ camera_devices->ForceUpdate();
+ }
+
+ if (nfc_devices) {
+ if (npad_id_type == NpadIdType::Handheld || npad_id_type == NpadIdType::Player1) {
+ nfc_devices->SetCallback({
+ .on_change =
+ [this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); },
+ });
+ nfc_devices->ForceUpdate();
+ }
+ }
+
// Use a common UUID for TAS
static constexpr Common::UUID TAS_UUID = Common::UUID{
{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
@@ -323,6 +353,8 @@ void EmulatedController::UnloadInput() {
for (auto& stick : tas_stick_devices) {
stick.reset();
}
+ camera_devices.reset();
+ nfc_devices.reset();
}
void EmulatedController::EnableConfiguration() {
@@ -340,6 +372,7 @@ void EmulatedController::DisableConfiguration() {
Disconnect();
}
SetNpadStyleIndex(tmp_npad_type);
+ original_npad_type = tmp_npad_type;
}
// Apply temporary connected status to the real controller
@@ -353,14 +386,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 +530,151 @@ 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;
+ 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;
- }
+ // 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;
+ }
- // Unlock button ready for next press
- if (!new_status.value && current_status.locked) {
- current_status.locked = false;
- }
+ // Unlock button ready for next press
+ if (!new_status.value && current_status.locked) {
+ current_status.locked = false;
}
+ }
+
+ if (!value_changed) {
+ return;
+ }
- if (!value_changed) {
+ if (is_configuring) {
+ controller.npad_button_state.raw = NpadButton::None;
+ controller.debug_pad_button_state.raw = 0;
+ lock.unlock();
+ TriggerOnChange(ControllerTriggerType::Button, false);
+ return;
+ }
+
+ // GC controllers have triggers not buttons
+ if (npad_type == NpadStyleIndex::GameCube) {
+ if (index == Settings::NativeButton::ZR) {
return;
}
-
- if (is_configuring) {
- controller.npad_button_state.raw = NpadButton::None;
- controller.debug_pad_button_state.raw = 0;
- TriggerOnChange(ControllerTriggerType::Button, false);
+ if (index == Settings::NativeButton::ZL) {
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;
- }
- controller.home_button_state.home.Assign(current_status.value);
+ 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 +691,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 +707,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 +734,7 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
break;
}
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Stick, true);
}
@@ -693,7 +743,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,10 +759,16 @@ 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;
}
+ // Only GC controllers have analog triggers
+ if (npad_type != NpadStyleIndex::GameCube) {
+ return;
+ }
+
const auto& trigger = controller.trigger_values[index];
switch (index) {
@@ -727,6 +783,7 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
break;
}
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Trigger, true);
}
@@ -735,7 +792,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 +813,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 +825,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 +834,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,9 +895,49 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
};
break;
}
+
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Battery, true);
}
+void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) {
+ std::unique_lock lock{mutex};
+ controller.camera_values = TransformToCamera(callback);
+
+ if (is_configuring) {
+ lock.unlock();
+ TriggerOnChange(ControllerTriggerType::IrSensor, false);
+ return;
+ }
+
+ controller.camera_state.sample++;
+ controller.camera_state.format =
+ static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format);
+ controller.camera_state.data = controller.camera_values.data;
+
+ lock.unlock();
+ TriggerOnChange(ControllerTriggerType::IrSensor, true);
+}
+
+void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
+ std::unique_lock lock{mutex};
+ controller.nfc_values = TransformToNfc(callback);
+
+ if (is_configuring) {
+ lock.unlock();
+ TriggerOnChange(ControllerTriggerType::Nfc, false);
+ return;
+ }
+
+ controller.nfc_state = {
+ controller.nfc_values.state,
+ controller.nfc_values.data,
+ };
+
+ lock.unlock();
+ TriggerOnChange(ControllerTriggerType::Nfc, true);
+}
+
bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
if (device_index >= output_devices.size()) {
return false;
@@ -871,18 +971,100 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v
}
bool EmulatedController::TestVibration(std::size_t device_index) {
- static constexpr VibrationValue test_vibration = {
+ if (device_index >= output_devices.size()) {
+ return false;
+ }
+ if (!output_devices[device_index]) {
+ return false;
+ }
+
+ const auto player_index = NpadIdTypeToIndex(npad_id_type);
+ const auto& player = Settings::values.players.GetValue()[player_index];
+
+ if (!player.vibration_enabled) {
+ return false;
+ }
+
+ const Common::Input::VibrationStatus test_vibration = {
.low_amplitude = 0.001f,
- .low_frequency = 160.0f,
+ .low_frequency = DEFAULT_VIBRATION_VALUE.low_frequency,
.high_amplitude = 0.001f,
- .high_frequency = 320.0f,
+ .high_frequency = DEFAULT_VIBRATION_VALUE.high_frequency,
+ .type = Common::Input::VibrationAmplificationType::Test,
+ };
+
+ const Common::Input::VibrationStatus zero_vibration = {
+ .low_amplitude = DEFAULT_VIBRATION_VALUE.low_amplitude,
+ .low_frequency = DEFAULT_VIBRATION_VALUE.low_frequency,
+ .high_amplitude = DEFAULT_VIBRATION_VALUE.high_amplitude,
+ .high_frequency = DEFAULT_VIBRATION_VALUE.high_frequency,
+ .type = Common::Input::VibrationAmplificationType::Test,
};
// Send a slight vibration to test for rumble support
- SetVibration(device_index, test_vibration);
+ output_devices[device_index]->SetVibration(test_vibration);
+
+ // Wait for about 15ms to ensure the controller is ready for the stop command
+ std::this_thread::sleep_for(std::chrono::milliseconds(15));
// Stop any vibration and return the result
- return SetVibration(device_index, DEFAULT_VIBRATION_VALUE);
+ return output_devices[device_index]->SetVibration(zero_vibration) ==
+ Common::Input::VibrationError::None;
+}
+
+bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) {
+ LOG_INFO(Service_HID, "Set polling mode {}", polling_mode);
+ auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
+ auto& nfc_output_device = output_devices[3];
+
+ const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
+ const auto mapped_nfc_result = output_device->SetPollingMode(polling_mode);
+
+ return virtual_nfc_result == Common::Input::PollingError::None ||
+ mapped_nfc_result == Common::Input::PollingError::None;
+}
+
+bool EmulatedController::SetCameraFormat(
+ Core::IrSensor::ImageTransferProcessorFormat camera_format) {
+ LOG_INFO(Service_HID, "Set camera format {}", camera_format);
+
+ auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
+ auto& camera_output_device = output_devices[2];
+
+ if (right_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
+ camera_format)) == Common::Input::CameraError::None) {
+ return true;
+ }
+
+ // Fallback to Qt camera if native device doesn't have support
+ return camera_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
+ camera_format)) == Common::Input::CameraError::None;
+}
+
+bool EmulatedController::HasNfc() const {
+ const auto& nfc_output_device = output_devices[3];
+
+ switch (npad_type) {
+ case NpadStyleIndex::JoyconRight:
+ case NpadStyleIndex::JoyconDual:
+ case NpadStyleIndex::ProController:
+ break;
+ default:
+ return false;
+ }
+
+ const bool has_virtual_nfc =
+ npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld;
+ const bool is_virtual_nfc_supported =
+ nfc_output_device->SupportsNfc() != Common::Input::NfcState::NotSupported;
+
+ return is_connected && (has_virtual_nfc && is_virtual_nfc_supported);
+}
+
+bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
+ auto& nfc_output_device = output_devices[3];
+
+ return nfc_output_device->WriteNfcData(data) == Common::Input::NfcState::Success;
}
void EmulatedController::SetLedPattern() {
@@ -907,13 +1089,27 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
if (!is_connected) {
return;
}
+
+ // Attempt to reconnect with the original type
+ if (npad_type != original_npad_type) {
+ Disconnect();
+ const auto current_npad_type = npad_type;
+ SetNpadStyleIndex(original_npad_type);
+ if (IsControllerSupported()) {
+ Connect();
+ return;
+ }
+ SetNpadStyleIndex(current_npad_type);
+ Connect();
+ }
+
if (IsControllerSupported()) {
return;
}
Disconnect();
- // Fallback fullkey controllers to Pro controllers
+ // Fallback Fullkey controllers to Pro controllers
if (IsControllerFullkey() && supported_style_tag.fullkey) {
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
SetNpadStyleIndex(NpadStyleIndex::ProController);
@@ -921,11 +1117,28 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
return;
}
+ // Fallback Dual joycon controllers to Pro controllers
+ if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) {
+ LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
+ SetNpadStyleIndex(NpadStyleIndex::ProController);
+ Connect();
+ return;
+ }
+
+ // Fallback Pro controllers to Dual joycon
+ if (npad_type == NpadStyleIndex::ProController && supported_style_tag.joycon_dual) {
+ LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type);
+ SetNpadStyleIndex(NpadStyleIndex::JoyconDual);
+ Connect();
+ return;
+ }
+
LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller",
npad_type);
}
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:
@@ -941,6 +1154,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:
@@ -976,40 +1190,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;
}
@@ -1023,10 +1241,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;
}
@@ -1034,27 +1254,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);
}
@@ -1082,30 +1303,42 @@ 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;
}
+CameraValues EmulatedController::GetCameraValues() const {
+ std::scoped_lock lock{mutex};
+ return controller.camera_values;
+}
+
HomeButtonState EmulatedController::GetHomeButtons() const {
+ std::scoped_lock lock{mutex};
if (is_configuring) {
return {};
}
@@ -1113,6 +1346,7 @@ HomeButtonState EmulatedController::GetHomeButtons() const {
}
CaptureButtonState EmulatedController::GetCaptureButtons() const {
+ std::scoped_lock lock{mutex};
if (is_configuring) {
return {};
}
@@ -1120,6 +1354,7 @@ CaptureButtonState EmulatedController::GetCaptureButtons() const {
}
NpadButtonState EmulatedController::GetNpadButtons() const {
+ std::scoped_lock lock{mutex};
if (is_configuring) {
return {};
}
@@ -1127,6 +1362,7 @@ NpadButtonState EmulatedController::GetNpadButtons() const {
}
DebugPadButton EmulatedController::GetDebugPadButtons() const {
+ std::scoped_lock lock{mutex};
if (is_configuring) {
return {};
}
@@ -1134,20 +1370,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 {};
}
@@ -1155,26 +1398,54 @@ 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;
}
+const CameraState& EmulatedController::GetCamera() const {
+ std::scoped_lock lock{mutex};
+ return controller.camera_state;
+}
+
+const NfcState& EmulatedController::GetNfc() const {
+ std::scoped_lock lock{mutex};
+ return controller.nfc_state;
+}
+
+NpadColor EmulatedController::GetNpadColor(u32 color) {
+ return {
+ .r = static_cast<u8>((color >> 16) & 0xFF),
+ .g = static_cast<u8>((color >> 8) & 0xFF),
+ .b = static_cast<u8>(color & 0xFF),
+ .a = 0xff,
+ };
+}
+
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) {
@@ -1187,13 +1458,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 d8642c5b3..319226bf8 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -16,10 +15,12 @@
#include "common/settings.h"
#include "common/vector_math.h"
#include "core/hid/hid_types.h"
+#include "core/hid/irs_types.h"
#include "core/hid/motion_input.h"
namespace Core::HID {
const std::size_t max_emulated_controllers = 2;
+const std::size_t output_devices_size = 4;
struct ControllerMotionInfo {
Common::Input::MotionStatus raw_status{};
MotionInput emulated{};
@@ -35,15 +36,18 @@ using TriggerDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
using BatteryDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
-using OutputDevices =
- std::array<std::unique_ptr<Common::Input::OutputDevice>, max_emulated_controllers>;
+using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
+using NfcDevices = std::unique_ptr<Common::Input::InputDevice>;
+using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>;
using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
-using OutputParams = std::array<Common::ParamPackage, max_emulated_controllers>;
+using CameraParams = Common::ParamPackage;
+using NfcParams = Common::ParamPackage;
+using OutputParams = std::array<Common::ParamPackage, output_devices_size>;
using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
@@ -52,6 +56,8 @@ using TriggerValues =
using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>;
using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
+using CameraValues = Common::Input::CameraStatus;
+using NfcValues = Common::Input::NfcStatus;
using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
struct AnalogSticks {
@@ -71,6 +77,17 @@ struct BatteryLevelState {
NpadPowerInfo right{};
};
+struct CameraState {
+ Core::IrSensor::ImageTransferProcessorFormat format{};
+ std::vector<u8> data{};
+ std::size_t sample{};
+};
+
+struct NfcState {
+ Common::Input::NfcState state{};
+ std::vector<u8> data{};
+};
+
struct ControllerMotion {
Common::Vec3f accel{};
Common::Vec3f gyro{};
@@ -97,6 +114,8 @@ struct ControllerStatus {
ColorValues color_values{};
BatteryValues battery_values{};
VibrationValues vibration_values{};
+ CameraValues camera_values{};
+ NfcValues nfc_values{};
// Data for HID serices
HomeButtonState home_button_state{};
@@ -108,6 +127,8 @@ struct ControllerStatus {
NpadGcTriggerState gc_trigger_state{};
ControllerColors colors_state{};
BatteryLevelState battery_state{};
+ CameraState camera_state{};
+ NfcState nfc_state{};
};
enum class ControllerTriggerType {
@@ -118,6 +139,8 @@ enum class ControllerTriggerType {
Color,
Battery,
Vibration,
+ IrSensor,
+ Nfc,
Connected,
Disconnected,
Type,
@@ -270,6 +293,9 @@ public:
/// Returns the latest battery status from the controller with parameters
BatteryValues GetBatteryValues() const;
+ /// Returns the latest camera status from the controller with parameters
+ CameraValues GetCameraValues() const;
+
/// Returns the latest status of button input for the hid::HomeButton service
HomeButtonState GetHomeButtons() const;
@@ -297,18 +323,44 @@ public:
/// Returns the latest battery status from the controller
BatteryLevelState GetBattery() const;
+ /// Returns the latest camera status from the controller
+ const CameraState& GetCamera() const;
+
+ /// Returns the latest ntag status from the controller
+ const NfcState& GetNfc() const;
+
/**
* Sends a specific vibration to the output device
- * @return returns true if vibration had no errors
+ * @return true if vibration had no errors
*/
bool SetVibration(std::size_t device_index, VibrationValue vibration);
/**
* Sends a small vibration to the output device
- * @return returns true if SetVibration was successfull
+ * @return true if SetVibration was successfull
*/
bool TestVibration(std::size_t device_index);
+ /**
+ * Sets the desired data to be polled from a controller
+ * @param polling_mode type of input desired buttons, gyro, nfc, ir, etc.
+ * @return true if SetPollingMode was successfull
+ */
+ bool SetPollingMode(Common::Input::PollingMode polling_mode);
+
+ /**
+ * Sets the desired camera format to be polled from a controller
+ * @param camera_format size of each frame
+ * @return true if SetCameraFormat was successfull
+ */
+ bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format);
+
+ /// Returns true if the device has nfc support
+ bool HasNfc() const;
+
+ /// Returns true if the nfc tag was written
+ bool WriteNfc(const std::vector<u8>& data);
+
/// Returns the led pattern corresponding to this emulated controller
LedPattern GetLedPattern() const;
@@ -387,14 +439,34 @@ private:
void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index);
/**
+ * Updates the camera status of the controller
+ * @param callback A CallbackStatus containing the camera status
+ */
+ void SetCamera(const Common::Input::CallbackStatus& callback);
+
+ /**
+ * Updates the nfc status of the controller
+ * @param callback A CallbackStatus containing the nfc status
+ */
+ void SetNfc(const Common::Input::CallbackStatus& callback);
+
+ /**
+ * Converts a color format from bgra to rgba
+ * @param color in bgra format
+ * @return NpadColor in rgba format
+ */
+ NpadColor GetNpadColor(u32 color);
+
+ /**
* Triggers a callback that something has changed on the controller status
* @param type Input type of the event to trigger
* @param is_service_update indicates if this event should only be sent to HID services
*/
void TriggerOnChange(ControllerTriggerType type, bool is_service_update);
- NpadIdType npad_id_type;
+ const NpadIdType npad_id_type;
NpadStyleIndex npad_type{NpadStyleIndex::None};
+ NpadStyleIndex original_npad_type{NpadStyleIndex::None};
NpadStyleTag supported_style_tag{NpadStyleSet::All};
bool is_connected{false};
bool is_configuring{false};
@@ -411,6 +483,8 @@ private:
ControllerMotionParams motion_params;
TriggerParams trigger_params;
BatteryParams battery_params;
+ CameraParams camera_params;
+ NfcParams nfc_params;
OutputParams output_params;
ButtonDevices button_devices;
@@ -418,6 +492,8 @@ private:
ControllerMotionDevices motion_devices;
TriggerDevices trigger_devices;
BatteryDevices battery_devices;
+ CameraDevices camera_devices;
+ NfcDevices nfc_devices;
OutputDevices output_devices;
// TAS related variables
@@ -427,6 +503,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..8d367b546 100644
--- a/src/core/hid/emulated_devices.cpp
+++ b/src/core/hid/emulated_devices.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <fmt/format.h>
@@ -15,6 +14,7 @@ EmulatedDevices::EmulatedDevices() = default;
EmulatedDevices::~EmulatedDevices() = default;
void EmulatedDevices::ReloadFromSettings() {
+ ring_params = Common::ParamPackage(Settings::values.ringcon_analogs);
ReloadInput();
}
@@ -66,6 +66,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 +122,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 +164,7 @@ void EmulatedDevices::SaveCurrentConfig() {
if (!is_configuring) {
return;
}
+ Settings::values.ringcon_analogs = ring_params.Serialize();
}
void EmulatedDevices::RestoreConfig() {
@@ -164,12 +174,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 +220,7 @@ void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& cal
}
if (is_configuring) {
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::Keyboard);
return;
}
@@ -208,6 +228,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 +248,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 +280,7 @@ void EmulatedDevices::SetKeyboardModifier(const Common::Input::CallbackStatus& c
}
if (is_configuring) {
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
return;
}
@@ -289,6 +311,7 @@ void EmulatedDevices::SetKeyboardModifier(const Common::Input::CallbackStatus& c
break;
}
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
}
@@ -297,7 +320,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 +352,7 @@ void EmulatedDevices::SetMouseButton(const Common::Input::CallbackStatus& callba
}
if (is_configuring) {
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
return;
}
@@ -351,6 +375,7 @@ void EmulatedDevices::SetMouseButton(const Common::Input::CallbackStatus& callba
break;
}
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
}
@@ -359,13 +384,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 +405,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 +425,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 +505,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..4149eeced 100644
--- a/src/core/hid/emulated_devices.h
+++ b/src/core/hid/emulated_devices.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -26,9 +25,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 +40,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 +58,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 +66,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 +119,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 +137,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 +155,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 +210,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 +223,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_core.cpp b/src/core/hid/hid_core.cpp
index a1c3bbb57..7d6373414 100644
--- a/src/core/hid/hid_core.cpp
+++ b/src/core/hid/hid_core.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "core/hid/emulated_console.h"
@@ -49,7 +48,7 @@ EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) {
return handheld.get();
case NpadIdType::Invalid:
default:
- UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
+ ASSERT_MSG(false, "Invalid NpadIdType={}", npad_id_type);
return nullptr;
}
}
@@ -78,7 +77,7 @@ const EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type
return handheld.get();
case NpadIdType::Invalid:
default:
- UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
+ ASSERT_MSG(false, "Invalid NpadIdType={}", npad_id_type);
return nullptr;
}
}
diff --git a/src/core/hid/hid_core.h b/src/core/hid/hid_core.h
index 717f605e7..5fe36551e 100644
--- a/src/core/hid/hid_core.h
+++ b/src/core/hid/hid_core.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h
index 778b328b9..e3b1cfbc6 100644
--- a/src/core/hid/hid_types.h
+++ b/src/core/hid/hid_types.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -273,6 +272,7 @@ enum class VibrationDeviceType : u32 {
Unknown = 0,
LinearResonantActuator = 1,
GcErm = 2,
+ N64 = 3,
};
// This is nn::hid::VibrationGcErmCommand
@@ -317,27 +317,35 @@ static_assert(sizeof(TouchAttribute) == 0x4, "TouchAttribute is an invalid size"
// This is nn::hid::TouchState
struct TouchState {
- u64 delta_time;
- TouchAttribute attribute;
- u32 finger;
- Common::Point<u32> position;
- u32 diameter_x;
- u32 diameter_y;
- u32 rotation_angle;
+ u64 delta_time{};
+ TouchAttribute attribute{};
+ u32 finger{};
+ Common::Point<u32> position{};
+ u32 diameter_x{};
+ u32 diameter_y{};
+ u32 rotation_angle{};
};
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
+struct NpadColor {
+ u8 r{};
+ u8 g{};
+ u8 b{};
+ u8 a{};
+};
+static_assert(sizeof(NpadColor) == 4, "NpadColor is an invalid size");
+
// This is nn::hid::NpadControllerColor
struct NpadControllerColor {
- u32 body;
- u32 button;
+ NpadColor body{};
+ NpadColor button{};
};
static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size");
// This is nn::hid::AnalogStickState
struct AnalogStickState {
- s32 x;
- s32 y;
+ s32 x{};
+ s32 y{};
};
static_assert(sizeof(AnalogStickState) == 8, "AnalogStickState is an invalid size");
@@ -355,10 +363,10 @@ static_assert(sizeof(NpadBatteryLevel) == 0x4, "NpadBatteryLevel is an invalid s
// This is nn::hid::system::NpadPowerInfo
struct NpadPowerInfo {
- bool is_powered;
- bool is_charging;
+ bool is_powered{};
+ bool is_charging{};
INSERT_PADDING_BYTES(0x6);
- NpadBatteryLevel battery_level;
+ NpadBatteryLevel battery_level{8};
};
static_assert(sizeof(NpadPowerInfo) == 0xC, "NpadPowerInfo is an invalid size");
@@ -475,8 +483,8 @@ static_assert(sizeof(DebugPadButton) == 0x4, "DebugPadButton is an invalid size"
// This is nn::hid::ConsoleSixAxisSensorHandle
struct ConsoleSixAxisSensorHandle {
- u8 unknown_1;
- u8 unknown_2;
+ u8 unknown_1{};
+ u8 unknown_2{};
INSERT_PADDING_BYTES_NOINIT(2);
};
static_assert(sizeof(ConsoleSixAxisSensorHandle) == 4,
@@ -484,35 +492,79 @@ static_assert(sizeof(ConsoleSixAxisSensorHandle) == 4,
// This is nn::hid::SixAxisSensorHandle
struct SixAxisSensorHandle {
- NpadStyleIndex npad_type;
- u8 npad_id;
- DeviceIndex device_index;
+ NpadStyleIndex npad_type{NpadStyleIndex::None};
+ u8 npad_id{};
+ DeviceIndex device_index{DeviceIndex::None};
INSERT_PADDING_BYTES_NOINIT(1);
};
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");
+// This is nn::hid::server::SixAxisSensorProperties
+struct SixAxisSensorProperties {
+ union {
+ u8 raw{};
+ BitField<0, 1, u8> is_newly_assigned;
+ BitField<1, 1, u8> is_firmware_update_available;
+ };
+};
+static_assert(sizeof(SixAxisSensorProperties) == 1, "SixAxisSensorProperties is an invalid size");
+
+// This is nn::hid::SixAxisSensorCalibrationParameter
+struct SixAxisSensorCalibrationParameter {
+ std::array<u8, 0x744> unknown_data{};
+};
+static_assert(sizeof(SixAxisSensorCalibrationParameter) == 0x744,
+ "SixAxisSensorCalibrationParameter is an invalid size");
+
+// This is nn::hid::SixAxisSensorIcInformation
+struct SixAxisSensorIcInformation {
+ f32 angular_rate{2000.0f}; // dps
+ std::array<f32, 6> unknown_gyro_data1{
+ -10.0f, -10.0f, -10.0f, 10.0f, 10.0f, 10.0f,
+ }; // dps
+ std::array<f32, 9> unknown_gyro_data2{
+ 0.95f, -0.003f, -0.003f, -0.003f, 0.95f, -0.003f, -0.003f, -0.003f, 0.95f,
+ };
+ std::array<f32, 9> unknown_gyro_data3{
+ 1.05f, 0.003f, 0.003f, 0.003f, 1.05f, 0.003f, 0.003f, 0.003f, 1.05f,
+ };
+ f32 acceleration_range{8.0f}; // g force
+ std::array<f32, 6> unknown_accel_data1{
+ -0.0612f, -0.0612f, -0.0612f, 0.0612f, 0.0612f, 0.0612f,
+ }; // g force
+ std::array<f32, 9> unknown_accel_data2{
+ 0.95f, -0.003f, -0.003f, -0.003f, 0.95f, -0.003f, -0.003f, -0.003f, 0.95f,
+ };
+ std::array<f32, 9> unknown_accel_data3{
+ 1.05f, 0.003f, 0.003f, 0.003f, 1.05f, 0.003f, 0.003f, 0.003f, 1.05f,
+ };
+};
+static_assert(sizeof(SixAxisSensorIcInformation) == 0xC8,
+ "SixAxisSensorIcInformation is an invalid size");
+
// This is nn::hid::VibrationDeviceHandle
struct VibrationDeviceHandle {
- NpadStyleIndex npad_type;
- u8 npad_id;
- DeviceIndex device_index;
+ NpadStyleIndex npad_type{NpadStyleIndex::None};
+ u8 npad_id{};
+ DeviceIndex device_index{DeviceIndex::None};
INSERT_PADDING_BYTES_NOINIT(1);
};
static_assert(sizeof(VibrationDeviceHandle) == 4, "SixAxisSensorHandle is an invalid size");
// This is nn::hid::VibrationValue
struct VibrationValue {
- f32 low_amplitude;
- f32 low_frequency;
- f32 high_amplitude;
- f32 high_frequency;
+ f32 low_amplitude{};
+ f32 low_frequency{};
+ f32 high_amplitude{};
+ f32 high_frequency{};
};
static_assert(sizeof(VibrationValue) == 0x10, "VibrationValue has incorrect size.");
@@ -561,7 +613,7 @@ static_assert(sizeof(KeyboardAttribute) == 0x4, "KeyboardAttribute is an invalid
// This is nn::hid::KeyboardKey
struct KeyboardKey {
// This should be a 256 bit flag
- std::array<u8, 32> key;
+ std::array<u8, 32> key{};
};
static_assert(sizeof(KeyboardKey) == 0x20, "KeyboardKey is an invalid size");
@@ -590,16 +642,16 @@ static_assert(sizeof(MouseAttribute) == 0x4, "MouseAttribute is an invalid size"
// This is nn::hid::detail::MouseState
struct MouseState {
- s64 sampling_number;
- s32 x;
- s32 y;
- s32 delta_x;
- s32 delta_y;
+ s64 sampling_number{};
+ s32 x{};
+ s32 y{};
+ s32 delta_x{};
+ s32 delta_y{};
// Axis Order in HW is switched for the wheel
- s32 delta_wheel_y;
- s32 delta_wheel_x;
- MouseButton button;
- MouseAttribute attribute;
+ s32 delta_wheel_y{};
+ s32 delta_wheel_x{};
+ MouseButton button{};
+ MouseAttribute attribute{};
};
static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size");
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index cd41607a7..fe9915abe 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -1,7 +1,7 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <algorithm>
#include <random>
#include "common/input.h"
@@ -52,6 +52,9 @@ Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatu
Common::Input::ButtonStatus status{};
switch (callback.type) {
case Common::Input::InputType::Analog:
+ status.value = TransformToTrigger(callback).pressed.value;
+ status.toggle = callback.analog_status.properties.toggle;
+ break;
case Common::Input::InputType::Trigger:
status.value = TransformToTrigger(callback).pressed.value;
break;
@@ -197,6 +200,9 @@ Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus&
x = std::clamp(x, 0.0f, 1.0f);
y = std::clamp(y, 0.0f, 1.0f);
+ // Limit id to maximum number of fingers
+ status.id = std::clamp(status.id, 0, 16);
+
if (status.pressed.inverted) {
status.pressed.value = !status.pressed.value;
}
@@ -267,6 +273,34 @@ Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatu
return status;
}
+Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback) {
+ Common::Input::CameraStatus camera{};
+ switch (callback.type) {
+ case Common::Input::InputType::IrSensor:
+ camera = callback.camera_status;
+ break;
+ default:
+ LOG_ERROR(Input, "Conversion from type {} to camera not implemented", callback.type);
+ break;
+ }
+
+ return camera;
+}
+
+Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback) {
+ Common::Input::NfcStatus nfc{};
+ switch (callback.type) {
+ case Common::Input::InputType::Nfc:
+ return callback.nfc_status;
+ break;
+ default:
+ LOG_ERROR(Input, "Conversion from type {} to NFC not implemented", callback.type);
+ break;
+ }
+
+ return nfc;
+}
+
void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
const auto& properties = analog.properties;
float& raw_value = analog.raw_value;
@@ -328,7 +362,7 @@ void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogS
raw_y += properties_y.offset;
// Apply X scale correction from offset
- if (std::abs(properties_x.offset) < 0.5f) {
+ if (std::abs(properties_x.offset) < 0.75f) {
if (raw_x > 0) {
raw_x /= 1 + properties_x.offset;
} else {
@@ -337,7 +371,7 @@ void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogS
}
// Apply Y scale correction from offset
- if (std::abs(properties_y.offset) < 0.5f) {
+ if (std::abs(properties_y.offset) < 0.75f) {
if (raw_y > 0) {
raw_y /= 1 + properties_y.offset;
} else {
diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h
index d24582226..b7eb6e660 100644
--- a/src/core/hid/input_converter.h
+++ b/src/core/hid/input_converter.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -78,6 +77,22 @@ Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackSta
Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback);
/**
+ * Converts raw input data into a valid camera status.
+ *
+ * @param callback Supported callbacks: Camera.
+ * @return A valid CameraObject object.
+ */
+Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback);
+
+/**
+ * Converts raw input data into a valid nfc status.
+ *
+ * @param callback Supported callbacks: Nfc.
+ * @return A valid CameraObject object.
+ */
+Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback);
+
+/**
* Converts raw analog data into a valid analog value
* @param analog An analog object containing raw data and properties
* @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f.
diff --git a/src/core/hid/input_interpreter.cpp b/src/core/hid/input_interpreter.cpp
index 2dbda8814..76d6b8ab0 100644
--- a/src/core/hid/input_interpreter.cpp
+++ b/src/core/hid/input_interpreter.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/hid/hid_types.h"
diff --git a/src/core/hid/input_interpreter.h b/src/core/hid/input_interpreter.h
index 70c34d474..8c521b381 100644
--- a/src/core/hid/input_interpreter.h
+++ b/src/core/hid/input_interpreter.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hid/irs_types.h b/src/core/hid/irs_types.h
new file mode 100644
index 000000000..88c5b016d
--- /dev/null
+++ b/src/core/hid/irs_types.h
@@ -0,0 +1,301 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/hid/hid_types.h"
+
+namespace Core::IrSensor {
+
+// This is nn::irsensor::CameraAmbientNoiseLevel
+enum class CameraAmbientNoiseLevel : u32 {
+ Low,
+ Medium,
+ High,
+ Unkown3, // This level can't be reached
+};
+
+// This is nn::irsensor::CameraLightTarget
+enum class CameraLightTarget : u32 {
+ AllLeds,
+ BrightLeds,
+ DimLeds,
+ None,
+};
+
+// This is nn::irsensor::PackedCameraLightTarget
+enum class PackedCameraLightTarget : u8 {
+ AllLeds,
+ BrightLeds,
+ DimLeds,
+ None,
+};
+
+// This is nn::irsensor::AdaptiveClusteringMode
+enum class AdaptiveClusteringMode : u32 {
+ StaticFov,
+ DynamicFov,
+};
+
+// This is nn::irsensor::AdaptiveClusteringTargetDistance
+enum class AdaptiveClusteringTargetDistance : u32 {
+ Near,
+ Middle,
+ Far,
+};
+
+// This is nn::irsensor::ImageTransferProcessorFormat
+enum class ImageTransferProcessorFormat : u32 {
+ Size320x240,
+ Size160x120,
+ Size80x60,
+ Size40x30,
+ Size20x15,
+};
+
+// This is nn::irsensor::PackedImageTransferProcessorFormat
+enum class PackedImageTransferProcessorFormat : u8 {
+ Size320x240,
+ Size160x120,
+ Size80x60,
+ Size40x30,
+ Size20x15,
+};
+
+// This is nn::irsensor::IrCameraStatus
+enum class IrCameraStatus : u32 {
+ Available,
+ Unsupported,
+ Unconnected,
+};
+
+// This is nn::irsensor::IrCameraInternalStatus
+enum class IrCameraInternalStatus : u32 {
+ Stopped,
+ FirmwareUpdateNeeded,
+ Unkown2,
+ Unkown3,
+ Unkown4,
+ FirmwareVersionRequested,
+ FirmwareVersionIsInvalid,
+ Ready,
+ Setting,
+};
+
+// This is nn::irsensor::detail::StatusManager::IrSensorMode
+enum class IrSensorMode : u64 {
+ None,
+ MomentProcessor,
+ ClusteringProcessor,
+ ImageTransferProcessor,
+ PointingProcessorMarker,
+ TeraPluginProcessor,
+ IrLedProcessor,
+};
+
+// This is nn::irsensor::ImageProcessorStatus
+enum ImageProcessorStatus : u32 {
+ Stopped,
+ Running,
+};
+
+// This is nn::irsensor::HandAnalysisMode
+enum class HandAnalysisMode : u32 {
+ None,
+ Silhouette,
+ Image,
+ SilhoueteAndImage,
+ SilhuetteOnly,
+};
+
+// This is nn::irsensor::IrSensorFunctionLevel
+enum class IrSensorFunctionLevel : u8 {
+ unknown0,
+ unknown1,
+ unknown2,
+ unknown3,
+ unknown4,
+};
+
+// This is nn::irsensor::MomentProcessorPreprocess
+enum class MomentProcessorPreprocess : u32 {
+ Unkown0,
+ Unkown1,
+};
+
+// This is nn::irsensor::PackedMomentProcessorPreprocess
+enum class PackedMomentProcessorPreprocess : u8 {
+ Unkown0,
+ Unkown1,
+};
+
+// This is nn::irsensor::PointingStatus
+enum class PointingStatus : u32 {
+ Unkown0,
+ Unkown1,
+};
+
+struct IrsRect {
+ s16 x;
+ s16 y;
+ s16 width;
+ s16 height;
+};
+
+struct IrsCentroid {
+ f32 x;
+ f32 y;
+};
+
+struct CameraConfig {
+ u64 exposure_time;
+ CameraLightTarget light_target;
+ u32 gain;
+ bool is_negative_used;
+ INSERT_PADDING_BYTES(7);
+};
+static_assert(sizeof(CameraConfig) == 0x18, "CameraConfig is an invalid size");
+
+struct PackedCameraConfig {
+ u64 exposure_time;
+ PackedCameraLightTarget light_target;
+ u8 gain;
+ bool is_negative_used;
+ INSERT_PADDING_BYTES(5);
+};
+static_assert(sizeof(PackedCameraConfig) == 0x10, "PackedCameraConfig is an invalid size");
+
+// This is nn::irsensor::IrCameraHandle
+struct IrCameraHandle {
+ u8 npad_id{};
+ Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None};
+ INSERT_PADDING_BYTES(2);
+};
+static_assert(sizeof(IrCameraHandle) == 4, "IrCameraHandle is an invalid size");
+
+// This is nn::irsensor::PackedMcuVersion
+struct PackedMcuVersion {
+ u16 major;
+ u16 minor;
+};
+static_assert(sizeof(PackedMcuVersion) == 4, "PackedMcuVersion is an invalid size");
+
+// This is nn::irsensor::PackedMomentProcessorConfig
+struct PackedMomentProcessorConfig {
+ PackedCameraConfig camera_config;
+ IrsRect window_of_interest;
+ PackedMcuVersion required_mcu_version;
+ PackedMomentProcessorPreprocess preprocess;
+ u8 preprocess_intensity_threshold;
+ INSERT_PADDING_BYTES(2);
+};
+static_assert(sizeof(PackedMomentProcessorConfig) == 0x20,
+ "PackedMomentProcessorConfig is an invalid size");
+
+// This is nn::irsensor::PackedClusteringProcessorConfig
+struct PackedClusteringProcessorConfig {
+ PackedCameraConfig camera_config;
+ IrsRect window_of_interest;
+ PackedMcuVersion required_mcu_version;
+ u32 pixel_count_min;
+ u32 pixel_count_max;
+ u8 object_intensity_min;
+ bool is_external_light_filter_enabled;
+ INSERT_PADDING_BYTES(2);
+};
+static_assert(sizeof(PackedClusteringProcessorConfig) == 0x28,
+ "PackedClusteringProcessorConfig is an invalid size");
+
+// This is nn::irsensor::PackedImageTransferProcessorConfig
+struct PackedImageTransferProcessorConfig {
+ PackedCameraConfig camera_config;
+ PackedMcuVersion required_mcu_version;
+ PackedImageTransferProcessorFormat format;
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(PackedImageTransferProcessorConfig) == 0x18,
+ "PackedImageTransferProcessorConfig is an invalid size");
+
+// This is nn::irsensor::PackedTeraPluginProcessorConfig
+struct PackedTeraPluginProcessorConfig {
+ PackedMcuVersion required_mcu_version;
+ u8 mode;
+ u8 unknown_1;
+ u8 unknown_2;
+ u8 unknown_3;
+};
+static_assert(sizeof(PackedTeraPluginProcessorConfig) == 0x8,
+ "PackedTeraPluginProcessorConfig is an invalid size");
+
+// This is nn::irsensor::PackedPointingProcessorConfig
+struct PackedPointingProcessorConfig {
+ IrsRect window_of_interest;
+ PackedMcuVersion required_mcu_version;
+};
+static_assert(sizeof(PackedPointingProcessorConfig) == 0xC,
+ "PackedPointingProcessorConfig is an invalid size");
+
+// This is nn::irsensor::PackedFunctionLevel
+struct PackedFunctionLevel {
+ IrSensorFunctionLevel function_level;
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(PackedFunctionLevel) == 0x4, "PackedFunctionLevel is an invalid size");
+
+// This is nn::irsensor::PackedImageTransferProcessorExConfig
+struct PackedImageTransferProcessorExConfig {
+ PackedCameraConfig camera_config;
+ PackedMcuVersion required_mcu_version;
+ PackedImageTransferProcessorFormat origin_format;
+ PackedImageTransferProcessorFormat trimming_format;
+ u16 trimming_start_x;
+ u16 trimming_start_y;
+ bool is_external_light_filter_enabled;
+ INSERT_PADDING_BYTES(5);
+};
+static_assert(sizeof(PackedImageTransferProcessorExConfig) == 0x20,
+ "PackedImageTransferProcessorExConfig is an invalid size");
+
+// This is nn::irsensor::PackedIrLedProcessorConfig
+struct PackedIrLedProcessorConfig {
+ PackedMcuVersion required_mcu_version;
+ u8 light_target;
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(PackedIrLedProcessorConfig) == 0x8,
+ "PackedIrLedProcessorConfig is an invalid size");
+
+// This is nn::irsensor::HandAnalysisConfig
+struct HandAnalysisConfig {
+ HandAnalysisMode mode;
+};
+static_assert(sizeof(HandAnalysisConfig) == 0x4, "HandAnalysisConfig is an invalid size");
+
+// This is nn::irsensor::detail::ProcessorState contents are different for each processor
+struct ProcessorState {
+ std::array<u8, 0xE20> processor_raw_data{};
+};
+static_assert(sizeof(ProcessorState) == 0xE20, "ProcessorState is an invalid size");
+
+// This is nn::irsensor::detail::DeviceFormat
+struct DeviceFormat {
+ Core::IrSensor::IrCameraStatus camera_status{Core::IrSensor::IrCameraStatus::Unconnected};
+ Core::IrSensor::IrCameraInternalStatus camera_internal_status{
+ Core::IrSensor::IrCameraInternalStatus::Ready};
+ Core::IrSensor::IrSensorMode mode{Core::IrSensor::IrSensorMode::None};
+ ProcessorState state{};
+};
+static_assert(sizeof(DeviceFormat) == 0xE30, "DeviceFormat is an invalid size");
+
+// This is nn::irsensor::ImageTransferProcessorState
+struct ImageTransferProcessorState {
+ u64 sampling_number;
+ Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
+ INSERT_PADDING_BYTES(4);
+};
+static_assert(sizeof(ImageTransferProcessorState) == 0x10,
+ "ImageTransferProcessorState is an invalid size");
+
+} // namespace Core::IrSensor
diff --git a/src/core/hid/motion_input.cpp b/src/core/hid/motion_input.cpp
index 05042fd99..b1f658e62 100644
--- a/src/core/hid/motion_input.cpp
+++ b/src/core/hid/motion_input.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/math_util.h"
#include "core/hid/motion_input.h"
diff --git a/src/core/hid/motion_input.h b/src/core/hid/motion_input.h
index bca4520fa..f5fd90db5 100644
--- a/src/core/hid/motion_input.h
+++ b/src/core/hid/motion_input.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/api_version.h b/src/core/hle/api_version.h
index 626e30753..bd15606e1 100644
--- a/src/core/hle/api_version.h
+++ b/src/core/hle/api_version.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h
index 602e12606..416da15ec 100644
--- a/src/core/hle/ipc.h
+++ b/src/core/hle/ipc.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index 026257115..d631c0357 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -19,7 +18,7 @@
namespace IPC {
-constexpr ResultCode ERR_REMOTE_PROCESS_DEAD{ErrorModule::HIPC, 301};
+constexpr Result ERR_REMOTE_PROCESS_DEAD{ErrorModule::HIPC, 301};
class RequestHelperBase {
protected:
@@ -176,7 +175,7 @@ public:
void PushImpl(float value);
void PushImpl(double value);
void PushImpl(bool value);
- void PushImpl(ResultCode value);
+ void PushImpl(Result value);
template <typename T>
void Push(T value) {
@@ -251,7 +250,7 @@ void ResponseBuilder::PushRaw(const T& value) {
index += (sizeof(T) + 3) / 4; // round up to word length
}
-inline void ResponseBuilder::PushImpl(ResultCode value) {
+inline void ResponseBuilder::PushImpl(Result value) {
// Result codes are actually 64-bit in the IPC buffer, but only the high part is discarded.
Push(value.raw);
Push<u32>(0);
@@ -385,7 +384,7 @@ public:
T PopRaw();
template <class T>
- std::shared_ptr<T> PopIpcInterface() {
+ std::weak_ptr<T> PopIpcInterface() {
ASSERT(context->Session()->IsDomain());
ASSERT(context->GetDomainMessageHeader().input_object_count > 0);
return context->GetDomainHandler<T>(Pop<u32>() - 1);
@@ -481,8 +480,8 @@ inline bool RequestParser::Pop() {
}
template <>
-inline ResultCode RequestParser::Pop() {
- return ResultCode{Pop<u32>()};
+inline Result RequestParser::Pop() {
+ return Result{Pop<u32>()};
}
template <typename T>
diff --git a/src/core/hle/kernel/arch/arm64/k_memory_region_device_types.inc b/src/core/hle/kernel/arch/arm64/k_memory_region_device_types.inc
index 857b512ba..2a0f2d5f7 100644
--- a/src/core/hle/kernel/arch/arm64/k_memory_region_device_types.inc
+++ b/src/core/hle/kernel/arch/arm64/k_memory_region_device_types.inc
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
// All architectures must define NumArchitectureDeviceRegions.
constexpr inline const auto NumArchitectureDeviceRegions = 3;
diff --git a/src/core/hle/kernel/board/nintendo/nx/k_memory_layout.h b/src/core/hle/kernel/board/nintendo/nx/k_memory_layout.h
new file mode 100644
index 000000000..d02ee61c3
--- /dev/null
+++ b/src/core/hle/kernel/board/nintendo/nx/k_memory_layout.h
@@ -0,0 +1,12 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Kernel {
+
+constexpr inline PAddr MainMemoryAddress = 0x80000000;
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/board/nintendo/nx/k_memory_region_device_types.inc b/src/core/hle/kernel/board/nintendo/nx/k_memory_region_device_types.inc
index 58d6c0b16..5f5ec6d5b 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_memory_region_device_types.inc
+++ b/src/core/hle/kernel/board/nintendo/nx/k_memory_region_device_types.inc
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
// All architectures must define NumBoardDeviceRegions.
constexpr inline const auto NumBoardDeviceRegions = 6;
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 6f335c251..c10b7bf30 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
@@ -1,10 +1,10 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <random>
#include "common/literals.h"
+#include "common/settings.h"
#include "core/hle/kernel/board/nintendo/nx/k_system_control.h"
#include "core/hle/kernel/board/nintendo/nx/secure_monitor.h"
@@ -28,33 +28,20 @@ namespace {
using namespace Common::Literals;
-u32 GetMemoryModeForInit() {
- return 0x01;
-}
-
u32 GetMemorySizeForInit() {
- return 0;
+ return Settings::values.use_extended_memory_layout ? Smc::MemorySize_6GB : Smc::MemorySize_4GB;
}
Smc::MemoryArrangement GetMemoryArrangeForInit() {
- switch (GetMemoryModeForInit() & 0x3F) {
- case 0x01:
- default:
- return Smc::MemoryArrangement_4GB;
- case 0x02:
- return Smc::MemoryArrangement_4GBForAppletDev;
- case 0x03:
- return Smc::MemoryArrangement_4GBForSystemDev;
- case 0x11:
- return Smc::MemoryArrangement_6GB;
- case 0x12:
- return Smc::MemoryArrangement_6GBForAppletDev;
- case 0x21:
- return Smc::MemoryArrangement_8GB;
- }
+ return Settings::values.use_extended_memory_layout ? Smc::MemoryArrangement_6GB
+ : Smc::MemoryArrangement_4GB;
}
} // namespace
+size_t KSystemControl::Init::GetRealMemorySize() {
+ return GetIntendedMemorySize();
+}
+
// Initialization.
size_t KSystemControl::Init::GetIntendedMemorySize() {
switch (GetMemorySizeForInit()) {
@@ -69,7 +56,13 @@ size_t KSystemControl::Init::GetIntendedMemorySize() {
}
PAddr KSystemControl::Init::GetKernelPhysicalBaseAddress(u64 base_address) {
- return base_address;
+ const size_t real_dram_size = KSystemControl::Init::GetRealMemorySize();
+ const size_t intended_dram_size = KSystemControl::Init::GetIntendedMemorySize();
+ if (intended_dram_size * 2 < real_dram_size) {
+ return base_address;
+ } else {
+ return base_address + ((real_dram_size - intended_dram_size) / 2);
+ }
}
bool KSystemControl::Init::ShouldIncreaseThreadResourceLimit() {
@@ -154,9 +147,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/board/nintendo/nx/k_system_control.h b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
index 52f230ced..fe375769e 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
+++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -13,6 +12,7 @@ public:
class Init {
public:
// Initialization.
+ static std::size_t GetRealMemorySize();
static std::size_t GetIntendedMemorySize();
static PAddr GetKernelPhysicalBaseAddress(u64 base_address);
static bool ShouldIncreaseThreadResourceLimit();
diff --git a/src/core/hle/kernel/board/nintendo/nx/secure_monitor.h b/src/core/hle/kernel/board/nintendo/nx/secure_monitor.h
index f77a91dec..b0e4123f0 100644
--- a/src/core/hle/kernel/board/nintendo/nx/secure_monitor.h
+++ b/src/core/hle/kernel/board/nintendo/nx/secure_monitor.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/code_set.cpp b/src/core/hle/kernel/code_set.cpp
index 1f434e9af..41386048b 100644
--- a/src/core/hle/kernel/code_set.cpp
+++ b/src/core/hle/kernel/code_set.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/code_set.h"
diff --git a/src/core/hle/kernel/code_set.h b/src/core/hle/kernel/code_set.h
index 5cc3b9829..5220dbcb6 100644
--- a/src/core/hle/kernel/code_set.h
+++ b/src/core/hle/kernel/code_set.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/global_scheduler_context.cpp b/src/core/hle/kernel/global_scheduler_context.cpp
index baad2c5d6..65576b8c4 100644
--- a/src/core/hle/kernel/global_scheduler_context.cpp
+++ b/src/core/hle/kernel/global_scheduler_context.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
@@ -42,12 +41,7 @@ void GlobalSchedulerContext::PreemptThreads() {
ASSERT(IsLocked());
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
const u32 priority = preemption_priorities[core_id];
- kernel.Scheduler(core_id).RotateScheduledQueue(core_id, priority);
-
- // Signal an interrupt occurred. For core 3, this is a certainty, as preemption will result
- // in the rotator thread being scheduled. For cores 0-2, this is to simulate or system
- // interrupts that may have occurred.
- kernel.PhysicalCore(core_id).Interrupt();
+ KScheduler::RotateScheduledQueue(kernel, core_id, priority);
}
}
diff --git a/src/core/hle/kernel/global_scheduler_context.h b/src/core/hle/kernel/global_scheduler_context.h
index 6f44b534f..67bb9852d 100644
--- a/src/core/hle/kernel/global_scheduler_context.h
+++ b/src/core/hle/kernel/global_scheduler_context.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,7 +7,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 +78,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 e19544c54..5b3feec66 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -17,7 +16,6 @@
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_process.h"
-#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
@@ -25,8 +23,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);
@@ -45,7 +50,7 @@ bool SessionRequestManager::HasSessionRequestHandler(const HLERequestContext& co
LOG_CRITICAL(IPC, "object_id {} is too big!", object_id);
return false;
}
- return DomainHandler(object_id - 1) != nullptr;
+ return !DomainHandler(object_id - 1).expired();
} else {
return session_handler != nullptr;
}
@@ -55,7 +60,7 @@ void SessionRequestHandler::ClientConnected(KServerSession* session) {
session->ClientConnected(shared_from_this());
// Ensure our server session is tracked globally.
- kernel.RegisterServerSession(session);
+ kernel.RegisterServerObject(session);
}
void SessionRequestHandler::ClientDisconnected(KServerSession* session) {
@@ -183,8 +188,8 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
rp.Skip(1, false); // The command is actually an u64, but we don't use the high part.
}
-ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const KHandleTable& handle_table,
- u32_le* src_cmdbuf) {
+Result HLERequestContext::PopulateFromIncomingCommandBuffer(const KHandleTable& handle_table,
+ u32_le* src_cmdbuf) {
ParseCommandBuffer(handle_table, src_cmdbuf, true);
if (command_header->IsCloseCommand()) {
@@ -197,7 +202,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const KHandleTab
return ResultSuccess;
}
-ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_thread) {
+Result HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_thread) {
auto current_offset = handles_offset;
auto& owner_process = *requesting_thread.GetOwnerProcess();
auto& handle_table = owner_process.GetHandleTable();
@@ -282,15 +287,49 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
BufferDescriptorB().size() > buffer_index &&
BufferDescriptorB()[buffer_index].Size() >= size,
{ return 0; }, "BufferDescriptorB is invalid, index={}, size={}", buffer_index, size);
- memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
+ WriteBufferB(buffer, size, buffer_index);
} else {
ASSERT_OR_EXECUTE_MSG(
BufferDescriptorC().size() > buffer_index &&
BufferDescriptorC()[buffer_index].Size() >= size,
{ return 0; }, "BufferDescriptorC is invalid, index={}, size={}", buffer_index, size);
- memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
+ WriteBufferC(buffer, size, buffer_index);
+ }
+
+ return size;
+}
+
+std::size_t HLERequestContext::WriteBufferB(const void* buffer, std::size_t size,
+ std::size_t buffer_index) const {
+ if (buffer_index >= BufferDescriptorB().size() || size == 0) {
+ return 0;
+ }
+
+ const auto buffer_size{BufferDescriptorB()[buffer_index].Size()};
+ if (size > buffer_size) {
+ LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
+ buffer_size);
+ size = buffer_size; // TODO(bunnei): This needs to be HW tested
+ }
+
+ memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
+ return size;
+}
+
+std::size_t HLERequestContext::WriteBufferC(const void* buffer, std::size_t size,
+ std::size_t buffer_index) const {
+ if (buffer_index >= BufferDescriptorC().size() || size == 0) {
+ return 0;
+ }
+
+ const auto buffer_size{BufferDescriptorC()[buffer_index].Size()};
+ if (size > buffer_size) {
+ LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
+ buffer_size);
+ size = buffer_size; // TODO(bunnei): This needs to be HW tested
}
+ memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
return size;
}
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index 754b41ff6..99265ce90 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -19,7 +18,7 @@
#include "core/hle/ipc.h"
#include "core/hle/kernel/svc_common.h"
-union ResultCode;
+union Result;
namespace Core::Memory {
class Memory;
@@ -33,6 +32,11 @@ namespace Service {
class ServiceFrameworkBase;
}
+enum class ServiceThreadType {
+ Default,
+ CreateNew,
+};
+
namespace Kernel {
class Domain;
@@ -57,7 +61,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();
/**
@@ -66,10 +71,10 @@ public:
* it should be used to differentiate which client (As in ClientSession) we're answering to.
* TODO(Subv): Use a wrapper structure to hold all the information relevant to
* this request (ServerSession, Originator thread, Translated command buffer, etc).
- * @returns ResultCode the result code of the translate operation.
+ * @returns Result the result code of the translate operation.
*/
- virtual ResultCode HandleSyncRequest(Kernel::KServerSession& session,
- Kernel::HLERequestContext& context) = 0;
+ virtual Result HandleSyncRequest(Kernel::KServerSession& session,
+ Kernel::HLERequestContext& context) = 0;
/**
* Signals that a client has just connected to this HLE handler and keeps the
@@ -94,6 +99,7 @@ protected:
std::weak_ptr<ServiceThread> service_thread;
};
+using SessionRequestHandlerWeakPtr = std::weak_ptr<SessionRequestHandler>;
using SessionRequestHandlerPtr = std::shared_ptr<SessionRequestHandler>;
/**
@@ -135,11 +141,11 @@ public:
if (index < DomainHandlerCount()) {
domain_handlers[index] = nullptr;
} else {
- UNREACHABLE_MSG("Unexpected handler index {}", index);
+ ASSERT_MSG(false, "Unexpected handler index {}", index);
}
}
- SessionRequestHandlerPtr DomainHandler(std::size_t index) const {
+ SessionRequestHandlerWeakPtr DomainHandler(std::size_t index) const {
ASSERT_MSG(index < DomainHandlerCount(), "Unexpected handler index {}", index);
return domain_handlers.at(index);
}
@@ -206,11 +212,10 @@ public:
}
/// Populates this context with data from the requesting process/thread.
- ResultCode PopulateFromIncomingCommandBuffer(const KHandleTable& handle_table,
- u32_le* src_cmdbuf);
+ Result PopulateFromIncomingCommandBuffer(const KHandleTable& handle_table, u32_le* src_cmdbuf);
/// Writes data from this context back to the requesting process/thread.
- ResultCode WriteToOutgoingCommandBuffer(KThread& requesting_thread);
+ Result WriteToOutgoingCommandBuffer(KThread& requesting_thread);
u32_le GetHipcCommand() const {
return command;
@@ -272,6 +277,14 @@ public:
std::size_t WriteBuffer(const void* buffer, std::size_t size,
std::size_t buffer_index = 0) const;
+ /// Helper function to write buffer B
+ std::size_t WriteBufferB(const void* buffer, std::size_t size,
+ std::size_t buffer_index = 0) const;
+
+ /// Helper function to write buffer C
+ std::size_t WriteBufferC(const void* buffer, std::size_t size,
+ std::size_t buffer_index = 0) const;
+
/* Helper function to write a buffer using the appropriate buffer descriptor
*
* @tparam T an arbitrary container that satisfies the
@@ -328,10 +341,10 @@ public:
template <typename T>
std::shared_ptr<T> GetDomainHandler(std::size_t index) const {
- return std::static_pointer_cast<T>(manager->DomainHandler(index));
+ return std::static_pointer_cast<T>(manager.lock()->DomainHandler(index).lock());
}
- void SetSessionRequestManager(std::shared_ptr<SessionRequestManager> manager_) {
+ void SetSessionRequestManager(std::weak_ptr<SessionRequestManager> manager_) {
manager = std::move(manager_);
}
@@ -374,7 +387,7 @@ private:
u32 handles_offset{};
u32 domain_offset{};
- std::shared_ptr<SessionRequestManager> manager;
+ std::weak_ptr<SessionRequestManager> manager;
KernelCore& kernel;
Core::Memory::Memory& memory;
diff --git a/src/core/hle/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp
index 36fc0944a..9b6b284d0 100644
--- a/src/core/hle/kernel/init/init_slab_setup.cpp
+++ b/src/core/hle/kernel/init/init_slab_setup.cpp
@@ -1,25 +1,28 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/core.h"
+#include "core/device_memory.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/init/init_slab_setup.h"
#include "core/hle/kernel/k_code_memory.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_memory_manager.h"
+#include "core/hle/kernel/k_page_buffer.h"
#include "core/hle/kernel/k_port.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_session.h"
#include "core/hle/kernel/k_shared_memory.h"
+#include "core/hle/kernel/k_shared_memory_info.h"
#include "core/hle/kernel/k_system_control.h"
#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/k_thread_local_page.h"
#include "core/hle/kernel/k_transfer_memory.h"
namespace Kernel::Init {
@@ -32,9 +35,13 @@ namespace Kernel::Init {
HANDLER(KEvent, (SLAB_COUNT(KEvent)), ##__VA_ARGS__) \
HANDLER(KPort, (SLAB_COUNT(KPort)), ##__VA_ARGS__) \
HANDLER(KSharedMemory, (SLAB_COUNT(KSharedMemory)), ##__VA_ARGS__) \
+ HANDLER(KSharedMemoryInfo, (SLAB_COUNT(KSharedMemory) * 8), ##__VA_ARGS__) \
HANDLER(KTransferMemory, (SLAB_COUNT(KTransferMemory)), ##__VA_ARGS__) \
HANDLER(KCodeMemory, (SLAB_COUNT(KCodeMemory)), ##__VA_ARGS__) \
HANDLER(KSession, (SLAB_COUNT(KSession)), ##__VA_ARGS__) \
+ HANDLER(KThreadLocalPage, \
+ (SLAB_COUNT(KProcess) + (SLAB_COUNT(KProcess) + SLAB_COUNT(KThread)) / 8), \
+ ##__VA_ARGS__) \
HANDLER(KResourceLimit, (SLAB_COUNT(KResourceLimit)), ##__VA_ARGS__)
namespace {
@@ -50,38 +57,46 @@ enum KSlabType : u32 {
// Constexpr counts.
constexpr size_t SlabCountKProcess = 80;
constexpr size_t SlabCountKThread = 800;
-constexpr size_t SlabCountKEvent = 700;
+constexpr size_t SlabCountKEvent = 900;
constexpr size_t SlabCountKInterruptEvent = 100;
-constexpr size_t SlabCountKPort = 256 + 0x20; // Extra 0x20 ports over Nintendo for homebrew.
+constexpr size_t SlabCountKPort = 384;
constexpr size_t SlabCountKSharedMemory = 80;
constexpr size_t SlabCountKTransferMemory = 200;
constexpr size_t SlabCountKCodeMemory = 10;
constexpr size_t SlabCountKDeviceAddressSpace = 300;
-constexpr size_t SlabCountKSession = 933;
+constexpr size_t SlabCountKSession = 1133;
constexpr size_t SlabCountKLightSession = 100;
constexpr size_t SlabCountKObjectName = 7;
constexpr size_t SlabCountKResourceLimit = 5;
constexpr size_t SlabCountKDebug = Core::Hardware::NUM_CPU_CORES;
-constexpr size_t SlabCountKAlpha = 1;
-constexpr size_t SlabCountKBeta = 6;
+constexpr size_t SlabCountKIoPool = 1;
+constexpr size_t SlabCountKIoRegion = 6;
constexpr size_t SlabCountExtraKThread = 160;
+/// Helper function to translate from the slab virtual address to the reserved location in physical
+/// memory.
+static PAddr TranslateSlabAddrToPhysical(KMemoryLayout& memory_layout, VAddr slab_addr) {
+ slab_addr -= memory_layout.GetSlabRegionAddress();
+ return slab_addr + Core::DramMemoryMap::SlabHeapBase;
+}
+
template <typename T>
VAddr InitializeSlabHeap(Core::System& system, KMemoryLayout& memory_layout, VAddr address,
size_t num_objects) {
- // TODO(bunnei): This is just a place holder. We should initialize the appropriate KSlabHeap for
- // kernel object type T with the backing kernel memory pointer once we emulate kernel memory.
const size_t size = Common::AlignUp(sizeof(T) * num_objects, alignof(void*));
VAddr start = Common::AlignUp(address, alignof(T));
- // This is intentionally empty. Once KSlabHeap is fully implemented, we can replace this with
- // the pointer to emulated memory to pass along. Until then, KSlabHeap will just allocate/free
- // host memory.
- void* backing_kernel_memory{};
+ // This should use the virtual memory address passed in, but currently, we do not setup the
+ // kernel virtual memory layout. Instead, we simply map these at a region of physical memory
+ // that we reserve for the slab heaps.
+ // TODO(bunnei): Fix this once we support the kernel virtual memory layout.
if (size > 0) {
+ void* backing_kernel_memory{
+ system.DeviceMemory().GetPointer(TranslateSlabAddrToPhysical(memory_layout, start))};
+
const KMemoryRegion* region = memory_layout.FindVirtual(start + size - 1);
ASSERT(region != nullptr);
ASSERT(region->IsDerivedFrom(KMemoryRegionType_KernelSlab));
@@ -91,6 +106,12 @@ VAddr InitializeSlabHeap(Core::System& system, KMemoryLayout& memory_layout, VAd
return start + size;
}
+size_t CalculateSlabHeapGapSize() {
+ constexpr size_t KernelSlabHeapGapSize = 2_MiB - 296_KiB;
+ static_assert(KernelSlabHeapGapSize <= KernelSlabHeapGapsSizeMax);
+ return KernelSlabHeapGapSize;
+}
+
} // namespace
KSlabResourceCounts KSlabResourceCounts::CreateDefault() {
@@ -109,8 +130,8 @@ KSlabResourceCounts KSlabResourceCounts::CreateDefault() {
.num_KObjectName = SlabCountKObjectName,
.num_KResourceLimit = SlabCountKResourceLimit,
.num_KDebug = SlabCountKDebug,
- .num_KAlpha = SlabCountKAlpha,
- .num_KBeta = SlabCountKBeta,
+ .num_KIoPool = SlabCountKIoPool,
+ .num_KIoRegion = SlabCountKIoRegion,
};
}
@@ -136,11 +157,34 @@ size_t CalculateTotalSlabHeapSize(const KernelCore& kernel) {
#undef ADD_SLAB_SIZE
// Add the reserved size.
- size += KernelSlabHeapGapsSize;
+ size += CalculateSlabHeapGapSize();
return size;
}
+void InitializeKPageBufferSlabHeap(Core::System& system) {
+ auto& kernel = system.Kernel();
+
+ const auto& counts = kernel.SlabResourceCounts();
+ const size_t num_pages =
+ counts.num_KProcess + counts.num_KThread + (counts.num_KProcess + counts.num_KThread) / 8;
+ const size_t slab_size = num_pages * PageSize;
+
+ // Reserve memory from the system resource limit.
+ ASSERT(kernel.GetSystemResourceLimit()->Reserve(LimitableResource::PhysicalMemory, slab_size));
+
+ // Allocate memory for the slab.
+ constexpr auto AllocateOption = KMemoryManager::EncodeOption(
+ KMemoryManager::Pool::System, KMemoryManager::Direction::FromFront);
+ const PAddr slab_address =
+ kernel.MemoryManager().AllocateAndOpenContinuous(num_pages, 1, AllocateOption);
+ ASSERT(slab_address != 0);
+
+ // Initialize the slabheap.
+ KPageBuffer::InitializeSlabHeap(kernel, system.DeviceMemory().GetPointer(slab_address),
+ slab_size);
+}
+
void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout) {
auto& kernel = system.Kernel();
@@ -160,13 +204,13 @@ void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout) {
}
// Create an array to represent the gaps between the slabs.
- const size_t total_gap_size = KernelSlabHeapGapsSize;
+ const size_t total_gap_size = CalculateSlabHeapGapSize();
std::array<size_t, slab_types.size()> slab_gaps;
- for (size_t i = 0; i < slab_gaps.size(); i++) {
+ for (auto& slab_gap : slab_gaps) {
// Note: This is an off-by-one error from Nintendo's intention, because GenerateRandomRange
// is inclusive. However, Nintendo also has the off-by-one error, and it's "harmless", so we
// will include it ourselves.
- slab_gaps[i] = KSystemControl::GenerateRandomRange(0, total_gap_size);
+ slab_gap = KSystemControl::GenerateRandomRange(0, total_gap_size);
}
// Sort the array, so that we can treat differences between values as offsets to the starts of
@@ -177,13 +221,21 @@ void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout) {
}
}
- for (size_t i = 0; i < slab_types.size(); i++) {
+ // Track the gaps, so that we can free them to the unused slab tree.
+ VAddr gap_start = address;
+ size_t gap_size = 0;
+
+ for (size_t i = 0; i < slab_gaps.size(); i++) {
// Add the random gap to the address.
- address += (i == 0) ? slab_gaps[0] : slab_gaps[i] - slab_gaps[i - 1];
+ const auto cur_gap = (i == 0) ? slab_gaps[0] : slab_gaps[i] - slab_gaps[i - 1];
+ address += cur_gap;
+ gap_size += cur_gap;
#define INITIALIZE_SLAB_HEAP(NAME, COUNT, ...) \
case KSlabType_##NAME: \
- address = InitializeSlabHeap<NAME>(system, memory_layout, address, COUNT); \
+ if (COUNT > 0) { \
+ address = InitializeSlabHeap<NAME>(system, memory_layout, address, COUNT); \
+ } \
break;
// Initialize the slabheap.
@@ -192,7 +244,13 @@ void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout) {
FOREACH_SLAB_TYPE(INITIALIZE_SLAB_HEAP)
// If we somehow get an invalid type, abort.
default:
- UNREACHABLE();
+ ASSERT_MSG(false, "Unknown slab type: {}", slab_types[i]);
+ }
+
+ // If we've hit the end of a gap, free it.
+ if (gap_start + gap_size != address) {
+ gap_start = address;
+ gap_size = 0;
}
}
}
diff --git a/src/core/hle/kernel/init/init_slab_setup.h b/src/core/hle/kernel/init/init_slab_setup.h
index a8f7e0918..13be63c87 100644
--- a/src/core/hle/kernel/init/init_slab_setup.h
+++ b/src/core/hle/kernel/init/init_slab_setup.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -32,12 +31,13 @@ struct KSlabResourceCounts {
size_t num_KObjectName;
size_t num_KResourceLimit;
size_t num_KDebug;
- size_t num_KAlpha;
- size_t num_KBeta;
+ size_t num_KIoPool;
+ size_t num_KIoRegion;
};
void InitializeSlabResourceCounts(KernelCore& kernel);
size_t CalculateTotalSlabHeapSize(const KernelCore& kernel);
+void InitializeKPageBufferSlabHeap(Core::System& system);
void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout);
} // namespace Kernel::Init
diff --git a/src/core/hle/kernel/initial_process.h b/src/core/hle/kernel/initial_process.h
new file mode 100644
index 000000000..af0fb23b6
--- /dev/null
+++ b/src/core/hle/kernel/initial_process.h
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "common/literals.h"
+#include "core/hle/kernel/board/nintendo/nx/k_memory_layout.h"
+#include "core/hle/kernel/board/nintendo/nx/k_system_control.h"
+
+namespace Kernel {
+
+using namespace Common::Literals;
+
+constexpr std::size_t InitialProcessBinarySizeMax = 12_MiB;
+
+static inline PAddr GetInitialProcessBinaryPhysicalAddress() {
+ return Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetKernelPhysicalBaseAddress(
+ MainMemoryAddress);
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_address_arbiter.cpp b/src/core/hle/kernel/k_address_arbiter.cpp
index 783c69858..f85b11557 100644
--- a/src/core/hle/kernel/k_address_arbiter.cpp
+++ b/src/core/hle/kernel/k_address_arbiter.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
@@ -49,7 +48,7 @@ bool DecrementIfLessThan(Core::System& system, s32* out, VAddr address, s32 valu
}
} else {
// Otherwise, clear our exclusive hold and finish
- monitor.ClearExclusive();
+ monitor.ClearExclusive(current_core);
}
// We're done.
@@ -78,7 +77,7 @@ bool UpdateIfEqual(Core::System& system, s32* out, VAddr address, s32 value, s32
}
} else {
// Otherwise, clear our exclusive hold and finish.
- monitor.ClearExclusive();
+ monitor.ClearExclusive(current_core);
}
// We're done.
@@ -91,8 +90,7 @@ public:
explicit ThreadQueueImplForKAddressArbiter(KernelCore& kernel_, KAddressArbiter::ThreadTree* t)
: KThreadQueue(kernel_), m_tree(t) {}
- void CancelWait(KThread* waiting_thread, ResultCode wait_result,
- bool cancel_timer_task) override {
+ void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override {
// If the thread is waiting on an address arbiter, remove it from the tree.
if (waiting_thread->IsWaitingForAddressArbiter()) {
m_tree->erase(m_tree->iterator_to(*waiting_thread));
@@ -109,13 +107,13 @@ private:
} // namespace
-ResultCode KAddressArbiter::Signal(VAddr addr, s32 count) {
+Result KAddressArbiter::Signal(VAddr addr, s32 count) {
// Perform signaling.
s32 num_waiters{};
{
KScopedSchedulerLock sl(kernel);
- auto it = thread_tree.nfind_light({addr, -1});
+ auto it = thread_tree.nfind_key({addr, -1});
while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
(it->GetAddressArbiterKey() == addr)) {
// End the thread's wait.
@@ -132,7 +130,7 @@ ResultCode KAddressArbiter::Signal(VAddr addr, s32 count) {
return ResultSuccess;
}
-ResultCode KAddressArbiter::SignalAndIncrementIfEqual(VAddr addr, s32 value, s32 count) {
+Result KAddressArbiter::SignalAndIncrementIfEqual(VAddr addr, s32 value, s32 count) {
// Perform signaling.
s32 num_waiters{};
{
@@ -148,7 +146,7 @@ ResultCode KAddressArbiter::SignalAndIncrementIfEqual(VAddr addr, s32 value, s32
return ResultInvalidState;
}
- auto it = thread_tree.nfind_light({addr, -1});
+ auto it = thread_tree.nfind_key({addr, -1});
while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
(it->GetAddressArbiterKey() == addr)) {
// End the thread's wait.
@@ -165,13 +163,13 @@ ResultCode KAddressArbiter::SignalAndIncrementIfEqual(VAddr addr, s32 value, s32
return ResultSuccess;
}
-ResultCode KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32 value, s32 count) {
+Result KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32 value, s32 count) {
// Perform signaling.
s32 num_waiters{};
{
[[maybe_unused]] const KScopedSchedulerLock sl(kernel);
- auto it = thread_tree.nfind_light({addr, -1});
+ auto it = thread_tree.nfind_key({addr, -1});
// Determine the updated value.
s32 new_value{};
if (count <= 0) {
@@ -233,9 +231,9 @@ ResultCode KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32
return ResultSuccess;
}
-ResultCode KAddressArbiter::WaitIfLessThan(VAddr addr, s32 value, bool decrement, s64 timeout) {
+Result KAddressArbiter::WaitIfLessThan(VAddr addr, s32 value, bool decrement, s64 timeout) {
// Prepare to wait.
- KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
+ KThread* cur_thread = GetCurrentThreadPointer(kernel);
ThreadQueueImplForKAddressArbiter wait_queue(kernel, std::addressof(thread_tree));
{
@@ -286,9 +284,9 @@ ResultCode KAddressArbiter::WaitIfLessThan(VAddr addr, s32 value, bool decrement
return cur_thread->GetWaitResult();
}
-ResultCode KAddressArbiter::WaitIfEqual(VAddr addr, s32 value, s64 timeout) {
+Result KAddressArbiter::WaitIfEqual(VAddr addr, s32 value, s64 timeout) {
// Prepare to wait.
- KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
+ KThread* cur_thread = GetCurrentThreadPointer(kernel);
ThreadQueueImplForKAddressArbiter wait_queue(kernel, std::addressof(thread_tree));
{
diff --git a/src/core/hle/kernel/k_address_arbiter.h b/src/core/hle/kernel/k_address_arbiter.h
index bf8b46665..e4085ae22 100644
--- a/src/core/hle/kernel/k_address_arbiter.h
+++ b/src/core/hle/kernel/k_address_arbiter.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -9,7 +8,7 @@
#include "core/hle/kernel/k_condition_variable.h"
#include "core/hle/kernel/svc_types.h"
-union ResultCode;
+union Result;
namespace Core {
class System;
@@ -26,8 +25,7 @@ public:
explicit KAddressArbiter(Core::System& system_);
~KAddressArbiter();
- [[nodiscard]] ResultCode SignalToAddress(VAddr addr, Svc::SignalType type, s32 value,
- s32 count) {
+ [[nodiscard]] Result SignalToAddress(VAddr addr, Svc::SignalType type, s32 value, s32 count) {
switch (type) {
case Svc::SignalType::Signal:
return Signal(addr, count);
@@ -36,12 +34,12 @@ public:
case Svc::SignalType::SignalAndModifyByWaitingCountIfEqual:
return SignalAndModifyByWaitingCountIfEqual(addr, value, count);
}
- UNREACHABLE();
+ ASSERT(false);
return ResultUnknown;
}
- [[nodiscard]] ResultCode WaitForAddress(VAddr addr, Svc::ArbitrationType type, s32 value,
- s64 timeout) {
+ [[nodiscard]] Result WaitForAddress(VAddr addr, Svc::ArbitrationType type, s32 value,
+ s64 timeout) {
switch (type) {
case Svc::ArbitrationType::WaitIfLessThan:
return WaitIfLessThan(addr, value, false, timeout);
@@ -50,16 +48,16 @@ public:
case Svc::ArbitrationType::WaitIfEqual:
return WaitIfEqual(addr, value, timeout);
}
- UNREACHABLE();
+ ASSERT(false);
return ResultUnknown;
}
private:
- [[nodiscard]] ResultCode Signal(VAddr addr, s32 count);
- [[nodiscard]] ResultCode SignalAndIncrementIfEqual(VAddr addr, s32 value, s32 count);
- [[nodiscard]] ResultCode SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32 value, s32 count);
- [[nodiscard]] ResultCode WaitIfLessThan(VAddr addr, s32 value, bool decrement, s64 timeout);
- [[nodiscard]] ResultCode WaitIfEqual(VAddr addr, s32 value, s64 timeout);
+ [[nodiscard]] Result Signal(VAddr addr, s32 count);
+ [[nodiscard]] Result SignalAndIncrementIfEqual(VAddr addr, s32 value, s32 count);
+ [[nodiscard]] Result SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32 value, s32 count);
+ [[nodiscard]] Result WaitIfLessThan(VAddr addr, s32 value, bool decrement, s64 timeout);
+ [[nodiscard]] Result WaitIfEqual(VAddr addr, s32 value, s64 timeout);
ThreadTree thread_tree;
diff --git a/src/core/hle/kernel/k_address_space_info.cpp b/src/core/hle/kernel/k_address_space_info.cpp
index ca29edc88..3e612a207 100644
--- a/src/core/hle/kernel/k_address_space_info.cpp
+++ b/src/core/hle/kernel/k_address_space_info.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
@@ -85,7 +84,7 @@ u64 KAddressSpaceInfo::GetAddressSpaceStart(std::size_t width, Type type) {
ASSERT(IsAllowedIndexForAddress(AddressSpaceIndices39Bit[index]));
return AddressSpaceInfos[AddressSpaceIndices39Bit[index]].address;
}
- UNREACHABLE();
+ ASSERT(false);
return 0;
}
@@ -102,7 +101,7 @@ std::size_t KAddressSpaceInfo::GetAddressSpaceSize(std::size_t width, Type type)
ASSERT(IsAllowed39BitType(type));
return AddressSpaceInfos[AddressSpaceIndices39Bit[index]].size;
}
- UNREACHABLE();
+ ASSERT(false);
return 0;
}
diff --git a/src/core/hle/kernel/k_address_space_info.h b/src/core/hle/kernel/k_address_space_info.h
index 06f31c6d5..69e9d77f2 100644
--- a/src/core/hle/kernel/k_address_space_info.h
+++ b/src/core/hle/kernel/k_address_space_info.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_affinity_mask.h b/src/core/hle/kernel/k_affinity_mask.h
index cf704ce87..b58716e90 100644
--- a/src/core/hle/kernel/k_affinity_mask.h
+++ b/src/core/hle/kernel/k_affinity_mask.h
@@ -1,9 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-// This file references various implementation details from Atmosphere, an open-source firmware for
-// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_auto_object.cpp b/src/core/hle/kernel/k_auto_object.cpp
index c99a9ebb7..691af8ccb 100644
--- a/src/core/hle/kernel/k_auto_object.cpp
+++ b/src/core/hle/kernel/k_auto_object.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/kernel.h"
diff --git a/src/core/hle/kernel/k_auto_object.h b/src/core/hle/kernel/k_auto_object.h
index 05779f2d5..2827763d5 100644
--- a/src/core/hle/kernel/k_auto_object.h
+++ b/src/core/hle/kernel/k_auto_object.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -19,7 +18,7 @@ namespace Kernel {
class KernelCore;
class KProcess;
-#define KERNEL_AUTOOBJECT_TRAITS(CLASS, BASE_CLASS) \
+#define KERNEL_AUTOOBJECT_TRAITS_IMPL(CLASS, BASE_CLASS, ATTRIBUTE) \
\
private: \
friend class ::Kernel::KClassTokenGenerator; \
@@ -41,16 +40,19 @@ public:
static constexpr const char* GetStaticTypeName() { \
return TypeName; \
} \
- virtual TypeObj GetTypeObj() const { \
+ virtual TypeObj GetTypeObj() ATTRIBUTE { \
return GetStaticTypeObj(); \
} \
- virtual const char* GetTypeName() const { \
+ virtual const char* GetTypeName() ATTRIBUTE { \
return GetStaticTypeName(); \
} \
\
private: \
constexpr bool operator!=(const TypeObj& rhs)
+#define KERNEL_AUTOOBJECT_TRAITS(CLASS, BASE_CLASS) \
+ KERNEL_AUTOOBJECT_TRAITS_IMPL(CLASS, BASE_CLASS, const override)
+
class KAutoObject {
protected:
class TypeObj {
@@ -83,15 +85,13 @@ protected:
};
private:
- KERNEL_AUTOOBJECT_TRAITS(KAutoObject, KAutoObject);
+ KERNEL_AUTOOBJECT_TRAITS_IMPL(KAutoObject, KAutoObject, const);
public:
explicit KAutoObject(KernelCore& kernel_) : kernel(kernel_) {
RegisterWithKernel();
}
- virtual ~KAutoObject() {
- UnregisterWithKernel();
- }
+ virtual ~KAutoObject() = default;
static KAutoObject* Create(KAutoObject* ptr);
@@ -163,11 +163,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_auto_object_container.cpp b/src/core/hle/kernel/k_auto_object_container.cpp
index d5f80d5b2..636b3f993 100644
--- a/src/core/hle/kernel/k_auto_object_container.cpp
+++ b/src/core/hle/kernel/k_auto_object_container.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
diff --git a/src/core/hle/kernel/k_auto_object_container.h b/src/core/hle/kernel/k_auto_object_container.h
index 697cc4289..badd75d2a 100644
--- a/src/core/hle/kernel/k_auto_object_container.h
+++ b/src/core/hle/kernel/k_auto_object_container.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_class_token.cpp b/src/core/hle/kernel/k_class_token.cpp
index 21e2fe494..cc2a0f7ca 100644
--- a/src/core/hle/kernel/k_class_token.cpp
+++ b/src/core/hle/kernel/k_class_token.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_class_token.h"
diff --git a/src/core/hle/kernel/k_class_token.h b/src/core/hle/kernel/k_class_token.h
index 980010150..c9001ae3d 100644
--- a/src/core/hle/kernel/k_class_token.h
+++ b/src/core/hle/kernel/k_class_token.h
@@ -1,11 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <atomic>
-
#include "common/bit_util.h"
#include "common/common_types.h"
@@ -52,6 +49,7 @@ private:
}
}
}
+ UNREACHABLE();
}();
template <typename T>
diff --git a/src/core/hle/kernel/k_client_port.cpp b/src/core/hle/kernel/k_client_port.cpp
index ef168fe87..3cb22ff4d 100644
--- a/src/core/hle/kernel/k_client_port.cpp
+++ b/src/core/hle/kernel/k_client_port.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2021 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/scope_exit.h"
#include "core/hle/kernel/hle_ipc.h"
@@ -59,8 +58,8 @@ bool KClientPort::IsSignaled() const {
return num_sessions < max_sessions;
}
-ResultCode KClientPort::CreateSession(KClientSession** out,
- std::shared_ptr<SessionRequestManager> session_manager) {
+Result KClientPort::CreateSession(KClientSession** out,
+ std::shared_ptr<SessionRequestManager> session_manager) {
// Reserve a new session from the resource limit.
KScopedResourceReservation session_reservation(kernel.CurrentProcess()->GetResourceLimit(),
LimitableResource::Sessions);
diff --git a/src/core/hle/kernel/k_client_port.h b/src/core/hle/kernel/k_client_port.h
index 54bb05e20..e17eff28f 100644
--- a/src/core/hle/kernel/k_client_port.h
+++ b/src/core/hle/kernel/k_client_port.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -53,8 +52,8 @@ public:
void Destroy() override;
bool IsSignaled() const override;
- ResultCode CreateSession(KClientSession** out,
- std::shared_ptr<SessionRequestManager> session_manager = nullptr);
+ Result CreateSession(KClientSession** out,
+ std::shared_ptr<SessionRequestManager> session_manager = nullptr);
private:
std::atomic<s32> num_sessions{};
diff --git a/src/core/hle/kernel/k_client_session.cpp b/src/core/hle/kernel/k_client_session.cpp
index 242582f8f..b2a887b14 100644
--- a/src/core/hle/kernel/k_client_session.cpp
+++ b/src/core/hle/kernel/k_client_session.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/k_client_session.h"
@@ -22,8 +21,8 @@ void KClientSession::Destroy() {
void KClientSession::OnServerClosed() {}
-ResultCode KClientSession::SendSyncRequest(KThread* thread, Core::Memory::Memory& memory,
- Core::Timing::CoreTiming& core_timing) {
+Result KClientSession::SendSyncRequest(KThread* thread, Core::Memory::Memory& memory,
+ Core::Timing::CoreTiming& core_timing) {
// Signal the server session that new data is available
return parent->GetServerSession().HandleSyncRequest(thread, memory, core_timing);
}
diff --git a/src/core/hle/kernel/k_client_session.h b/src/core/hle/kernel/k_client_session.h
index ad6cc4ed1..0c750d756 100644
--- a/src/core/hle/kernel/k_client_session.h
+++ b/src/core/hle/kernel/k_client_session.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,7 +9,7 @@
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/result.h"
-union ResultCode;
+union Result;
namespace Core::Memory {
class Memory;
@@ -47,8 +46,8 @@ public:
return parent;
}
- ResultCode SendSyncRequest(KThread* thread, Core::Memory::Memory& memory,
- Core::Timing::CoreTiming& core_timing);
+ Result SendSyncRequest(KThread* thread, Core::Memory::Memory& memory,
+ Core::Timing::CoreTiming& core_timing);
void OnServerClosed();
diff --git a/src/core/hle/kernel/k_code_memory.cpp b/src/core/hle/kernel/k_code_memory.cpp
index 0b225e8e0..da57ceb21 100644
--- a/src/core/hle/kernel/k_code_memory.cpp
+++ b/src/core/hle/kernel/k_code_memory.cpp
@@ -1,15 +1,13 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
#include "common/common_types.h"
#include "core/device_memory.h"
-#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_code_memory.h"
#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/k_memory_block.h"
-#include "core/hle/kernel/k_page_linked_list.h"
+#include "core/hle/kernel/k_page_group.h"
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/slab_helpers.h"
@@ -21,7 +19,7 @@ namespace Kernel {
KCodeMemory::KCodeMemory(KernelCore& kernel_)
: KAutoObjectWithSlabHeapAndContainer{kernel_}, m_lock(kernel_) {}
-ResultCode KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr, size_t size) {
+Result KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr, size_t size) {
// Set members.
m_owner = kernel.CurrentProcess();
@@ -29,10 +27,10 @@ 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 = {};
// Lock the memory.
- R_TRY(page_table.LockForCodeMemory(addr, size))
+ R_TRY(page_table.LockForCodeMemory(&m_page_group, addr, size))
// Clear the memory.
for (const auto& block : m_page_group.Nodes()) {
@@ -40,6 +38,7 @@ ResultCode KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr
}
// Set remaining tracking members.
+ m_owner->Open();
m_address = addr;
m_is_initialized = true;
m_is_owner_mapped = false;
@@ -53,11 +52,17 @@ void KCodeMemory::Finalize() {
// Unlock.
if (!m_is_mapped && !m_is_owner_mapped) {
const size_t size = m_page_group.GetNumPages() * PageSize;
- m_owner->PageTable().UnlockForCodeMemory(m_address, size);
+ m_owner->PageTable().UnlockForCodeMemory(m_address, size, m_page_group);
}
+
+ // Close the page group.
+ m_page_group = {};
+
+ // Close our reference to our owner.
+ m_owner->Close();
}
-ResultCode KCodeMemory::Map(VAddr address, size_t size) {
+Result KCodeMemory::Map(VAddr address, size_t size) {
// Validate the size.
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
@@ -77,7 +82,7 @@ ResultCode KCodeMemory::Map(VAddr address, size_t size) {
return ResultSuccess;
}
-ResultCode KCodeMemory::Unmap(VAddr address, size_t size) {
+Result KCodeMemory::Unmap(VAddr address, size_t size) {
// Validate the size.
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
@@ -94,7 +99,7 @@ ResultCode KCodeMemory::Unmap(VAddr address, size_t size) {
return ResultSuccess;
}
-ResultCode KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission perm) {
+Result KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission perm) {
// Validate the size.
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
@@ -114,7 +119,8 @@ ResultCode KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermis
k_perm = KMemoryPermission::UserReadExecute;
break;
default:
- break;
+ // Already validated by ControlCodeMemory svc
+ UNREACHABLE();
}
// Map the memory.
@@ -127,7 +133,7 @@ ResultCode KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermis
return ResultSuccess;
}
-ResultCode KCodeMemory::UnmapFromOwner(VAddr address, size_t size) {
+Result KCodeMemory::UnmapFromOwner(VAddr address, size_t size) {
// Validate the size.
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
diff --git a/src/core/hle/kernel/k_code_memory.h b/src/core/hle/kernel/k_code_memory.h
index e0ba19a53..2e7e1436a 100644
--- a/src/core/hle/kernel/k_code_memory.h
+++ b/src/core/hle/kernel/k_code_memory.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,7 +7,7 @@
#include "core/device_memory.h"
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_light_lock.h"
-#include "core/hle/kernel/k_page_linked_list.h"
+#include "core/hle/kernel/k_page_group.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/kernel/svc_types.h"
@@ -30,20 +29,20 @@ class KCodeMemory final
public:
explicit KCodeMemory(KernelCore& kernel_);
- ResultCode Initialize(Core::DeviceMemory& device_memory, VAddr address, size_t size);
- void Finalize();
+ Result Initialize(Core::DeviceMemory& device_memory, VAddr address, size_t size);
+ void Finalize() override;
- ResultCode Map(VAddr address, size_t size);
- ResultCode Unmap(VAddr address, size_t size);
- ResultCode MapToOwner(VAddr address, size_t size, Svc::MemoryPermission perm);
- ResultCode UnmapFromOwner(VAddr address, size_t size);
+ Result Map(VAddr address, size_t size);
+ Result Unmap(VAddr address, size_t size);
+ Result MapToOwner(VAddr address, size_t size, Svc::MemoryPermission perm);
+ Result UnmapFromOwner(VAddr address, size_t size);
- bool IsInitialized() const {
+ bool IsInitialized() const override {
return m_is_initialized;
}
static void PostDestroy([[maybe_unused]] uintptr_t arg) {}
- KProcess* GetOwner() const {
+ KProcess* GetOwner() const override {
return m_owner;
}
VAddr GetSourceAddress() const {
@@ -54,7 +53,7 @@ public:
}
private:
- KPageLinkedList m_page_group{};
+ KPageGroup m_page_group{};
KProcess* m_owner{};
VAddr m_address{};
KLightLock m_lock;
diff --git a/src/core/hle/kernel/k_condition_variable.cpp b/src/core/hle/kernel/k_condition_variable.cpp
index aadcc297a..124149697 100644
--- a/src/core/hle/kernel/k_condition_variable.cpp
+++ b/src/core/hle/kernel/k_condition_variable.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
@@ -9,7 +8,6 @@
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
-#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_thread_queue.h"
#include "core/hle/kernel/kernel.h"
@@ -63,8 +61,7 @@ public:
explicit ThreadQueueImplForKConditionVariableWaitForAddress(KernelCore& kernel_)
: KThreadQueue(kernel_) {}
- void CancelWait(KThread* waiting_thread, ResultCode wait_result,
- bool cancel_timer_task) override {
+ void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override {
// Remove the thread as a waiter from its owner.
waiting_thread->GetLockOwner()->RemoveWaiter(waiting_thread);
@@ -82,8 +79,7 @@ public:
KernelCore& kernel_, KConditionVariable::ThreadTree* t)
: KThreadQueue(kernel_), m_tree(t) {}
- void CancelWait(KThread* waiting_thread, ResultCode wait_result,
- bool cancel_timer_task) override {
+ void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override {
// Remove the thread as a waiter from its owner.
if (KThread* owner = waiting_thread->GetLockOwner(); owner != nullptr) {
owner->RemoveWaiter(waiting_thread);
@@ -107,8 +103,8 @@ KConditionVariable::KConditionVariable(Core::System& system_)
KConditionVariable::~KConditionVariable() = default;
-ResultCode KConditionVariable::SignalToAddress(VAddr addr) {
- KThread* owner_thread = kernel.CurrentScheduler()->GetCurrentThread();
+Result KConditionVariable::SignalToAddress(VAddr addr) {
+ KThread* owner_thread = GetCurrentThreadPointer(kernel);
// Signal the address.
{
@@ -128,7 +124,7 @@ ResultCode KConditionVariable::SignalToAddress(VAddr addr) {
}
// Write the value to userspace.
- ResultCode result{ResultSuccess};
+ Result result{ResultSuccess};
if (WriteToUser(system, addr, std::addressof(next_value))) [[likely]] {
result = ResultSuccess;
} else {
@@ -148,8 +144,8 @@ ResultCode KConditionVariable::SignalToAddress(VAddr addr) {
}
}
-ResultCode KConditionVariable::WaitForAddress(Handle handle, VAddr addr, u32 value) {
- KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
+Result KConditionVariable::WaitForAddress(Handle handle, VAddr addr, u32 value) {
+ KThread* cur_thread = GetCurrentThreadPointer(kernel);
ThreadQueueImplForKConditionVariableWaitForAddress wait_queue(kernel);
// Wait for the address.
@@ -244,7 +240,7 @@ void KConditionVariable::Signal(u64 cv_key, s32 count) {
{
KScopedSchedulerLock sl(kernel);
- auto it = thread_tree.nfind_light({cv_key, -1});
+ auto it = thread_tree.nfind_key({cv_key, -1});
while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
(it->GetConditionVariableKey() == cv_key)) {
KThread* target_thread = std::addressof(*it);
@@ -263,7 +259,7 @@ void KConditionVariable::Signal(u64 cv_key, s32 count) {
}
}
-ResultCode KConditionVariable::Wait(VAddr addr, u64 key, u32 value, s64 timeout) {
+Result KConditionVariable::Wait(VAddr addr, u64 key, u32 value, s64 timeout) {
// Prepare to wait.
KThread* cur_thread = GetCurrentThreadPointer(kernel);
ThreadQueueImplForKConditionVariableWaitConditionVariable wait_queue(
diff --git a/src/core/hle/kernel/k_condition_variable.h b/src/core/hle/kernel/k_condition_variable.h
index 5e4815d08..fad4ed011 100644
--- a/src/core/hle/kernel/k_condition_variable.h
+++ b/src/core/hle/kernel/k_condition_variable.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -26,12 +25,12 @@ public:
~KConditionVariable();
// Arbitration
- [[nodiscard]] ResultCode SignalToAddress(VAddr addr);
- [[nodiscard]] ResultCode WaitForAddress(Handle handle, VAddr addr, u32 value);
+ [[nodiscard]] Result SignalToAddress(VAddr addr);
+ [[nodiscard]] Result WaitForAddress(Handle handle, VAddr addr, u32 value);
// Condition variable
void Signal(u64 cv_key, s32 count);
- [[nodiscard]] ResultCode Wait(VAddr addr, u64 key, u32 value, s64 timeout);
+ [[nodiscard]] Result Wait(VAddr addr, u64 key, u32 value, s64 timeout);
private:
void SignalImpl(KThread* thread);
diff --git a/src/core/hle/kernel/k_event.cpp b/src/core/hle/kernel/k_event.cpp
index 0720efece..e52fafbc7 100644
--- a/src/core/hle/kernel/k_event.cpp
+++ b/src/core/hle/kernel/k_event.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_process.h"
@@ -14,7 +13,7 @@ KEvent::KEvent(KernelCore& kernel_)
KEvent::~KEvent() = default;
-void KEvent::Initialize(std::string&& name_) {
+void KEvent::Initialize(std::string&& name_, KProcess* owner_) {
// Increment reference count.
// Because reference count is one on creation, this will result
// in a reference count of two. Thus, when both readable and
@@ -30,10 +29,8 @@ void KEvent::Initialize(std::string&& name_) {
writable_event.Initialize(this, name_ + ":Writable");
// Set our owner process.
- owner = kernel.CurrentProcess();
- if (owner) {
- owner->Open();
- }
+ owner = owner_;
+ owner->Open();
// Mark initialized.
name = std::move(name_);
@@ -47,10 +44,8 @@ void KEvent::Finalize() {
void KEvent::PostDestroy(uintptr_t arg) {
// Release the event count resource the owner process holds.
KProcess* owner = reinterpret_cast<KProcess*>(arg);
- if (owner) {
- owner->GetResourceLimit()->Release(LimitableResource::Events, 1);
- owner->Close();
- }
+ owner->GetResourceLimit()->Release(LimitableResource::Events, 1);
+ owner->Close();
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_event.h b/src/core/hle/kernel/k_event.h
index 3d3ec99e2..2ff828feb 100644
--- a/src/core/hle/kernel/k_event.h
+++ b/src/core/hle/kernel/k_event.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -22,7 +21,7 @@ public:
explicit KEvent(KernelCore& kernel_);
~KEvent() override;
- void Initialize(std::string&& name);
+ void Initialize(std::string&& name, KProcess* owner_);
void Finalize() override;
diff --git a/src/core/hle/kernel/k_handle_table.cpp b/src/core/hle/kernel/k_handle_table.cpp
index cf95f0852..e830ca46e 100644
--- a/src/core/hle/kernel/k_handle_table.cpp
+++ b/src/core/hle/kernel/k_handle_table.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_handle_table.h"
@@ -9,7 +8,7 @@ namespace Kernel {
KHandleTable::KHandleTable(KernelCore& kernel_) : kernel{kernel_} {}
KHandleTable::~KHandleTable() = default;
-ResultCode KHandleTable::Finalize() {
+Result KHandleTable::Finalize() {
// Get the table and clear our record of it.
u16 saved_table_size = 0;
{
@@ -63,7 +62,7 @@ bool KHandleTable::Remove(Handle handle) {
return true;
}
-ResultCode KHandleTable::Add(Handle* out_handle, KAutoObject* obj, u16 type) {
+Result KHandleTable::Add(Handle* out_handle, KAutoObject* obj) {
KScopedDisableDispatch dd(kernel);
KScopedSpinLock lk(m_lock);
@@ -75,7 +74,7 @@ ResultCode KHandleTable::Add(Handle* out_handle, KAutoObject* obj, u16 type) {
const auto linear_id = this->AllocateLinearId();
const auto index = this->AllocateEntry();
- m_entry_infos[index].info = {.linear_id = linear_id, .type = type};
+ m_entry_infos[index].linear_id = linear_id;
m_objects[index] = obj;
obj->Open();
@@ -86,7 +85,7 @@ ResultCode KHandleTable::Add(Handle* out_handle, KAutoObject* obj, u16 type) {
return ResultSuccess;
}
-ResultCode KHandleTable::Reserve(Handle* out_handle) {
+Result KHandleTable::Reserve(Handle* out_handle) {
KScopedDisableDispatch dd(kernel);
KScopedSpinLock lk(m_lock);
@@ -116,7 +115,7 @@ void KHandleTable::Unreserve(Handle handle) {
}
}
-void KHandleTable::Register(Handle handle, KAutoObject* obj, u16 type) {
+void KHandleTable::Register(Handle handle, KAutoObject* obj) {
KScopedDisableDispatch dd(kernel);
KScopedSpinLock lk(m_lock);
@@ -132,7 +131,7 @@ void KHandleTable::Register(Handle handle, KAutoObject* obj, u16 type) {
// Set the entry.
ASSERT(m_objects[index] == nullptr);
- m_entry_infos[index].info = {.linear_id = static_cast<u16>(linear_id), .type = type};
+ m_entry_infos[index].linear_id = static_cast<u16>(linear_id);
m_objects[index] = obj;
obj->Open();
diff --git a/src/core/hle/kernel/k_handle_table.h b/src/core/hle/kernel/k_handle_table.h
index 87004a0f9..0864a737c 100644
--- a/src/core/hle/kernel/k_handle_table.h
+++ b/src/core/hle/kernel/k_handle_table.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -31,7 +30,7 @@ public:
explicit KHandleTable(KernelCore& kernel_);
~KHandleTable();
- ResultCode Initialize(s32 size) {
+ Result Initialize(s32 size) {
R_UNLESS(size <= static_cast<s32>(MaxTableSize), ResultOutOfMemory);
// Initialize all fields.
@@ -42,7 +41,7 @@ public:
m_free_head_index = -1;
// Free all entries.
- for (s32 i = 0; i < static_cast<s32>(m_table_size); ++i) {
+ for (s16 i = 0; i < static_cast<s16>(m_table_size); ++i) {
m_objects[i] = nullptr;
m_entry_infos[i].next_free_index = i - 1;
m_free_head_index = i;
@@ -61,7 +60,7 @@ public:
return m_max_count;
}
- ResultCode Finalize();
+ Result Finalize();
bool Remove(Handle handle);
template <typename T = KAutoObject>
@@ -101,20 +100,11 @@ public:
return this->template GetObjectWithoutPseudoHandle<T>(handle);
}
- ResultCode Reserve(Handle* out_handle);
+ Result Reserve(Handle* out_handle);
void Unreserve(Handle handle);
- template <typename T>
- ResultCode Add(Handle* out_handle, T* obj) {
- static_assert(std::is_base_of_v<KAutoObject, T>);
- return this->Add(out_handle, obj, obj->GetTypeObj().GetClassToken());
- }
-
- template <typename T>
- void Register(Handle handle, T* obj) {
- static_assert(std::is_base_of_v<KAutoObject, T>);
- return this->Register(handle, obj, obj->GetTypeObj().GetClassToken());
- }
+ Result Add(Handle* out_handle, KAutoObject* obj);
+ void Register(Handle handle, KAutoObject* obj);
template <typename T>
bool GetMultipleObjects(T** out, const Handle* handles, size_t num_handles) const {
@@ -160,9 +150,6 @@ public:
}
private:
- ResultCode Add(Handle* out_handle, KAutoObject* obj, u16 type);
- void Register(Handle handle, KAutoObject* obj, u16 type);
-
s32 AllocateEntry() {
ASSERT(m_count < m_table_size);
@@ -179,7 +166,7 @@ private:
ASSERT(m_count > 0);
m_objects[index] = nullptr;
- m_entry_infos[index].next_free_index = m_free_head_index;
+ m_entry_infos[index].next_free_index = static_cast<s16>(m_free_head_index);
m_free_head_index = index;
@@ -278,19 +265,13 @@ private:
}
union EntryInfo {
- struct {
- u16 linear_id;
- u16 type;
- } info;
- s32 next_free_index;
+ u16 linear_id;
+ s16 next_free_index;
constexpr u16 GetLinearId() const {
- return info.linear_id;
- }
- constexpr u16 GetType() const {
- return info.type;
+ return linear_id;
}
- constexpr s32 GetNextFreeIndex() const {
+ constexpr s16 GetNextFreeIndex() const {
return next_free_index;
}
};
diff --git a/src/core/hle/kernel/k_interrupt_manager.cpp b/src/core/hle/kernel/k_interrupt_manager.cpp
index e5dd39751..1b577a5b3 100644
--- a/src/core/hle/kernel/k_interrupt_manager.cpp
+++ b/src/core/hle/kernel/k_interrupt_manager.cpp
@@ -1,12 +1,12 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_interrupt_manager.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/physical_core.h"
namespace Kernel::KInterruptManager {
@@ -16,8 +16,10 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) {
return;
}
- auto& scheduler = kernel.Scheduler(core_id);
- auto& current_thread = *scheduler.GetCurrentThread();
+ // Acknowledge the interrupt.
+ kernel.PhysicalCore(core_id).ClearInterrupt();
+
+ auto& current_thread = GetCurrentThread(kernel);
// If the user disable count is set, we may need to pin the current thread.
if (current_thread.GetUserDisableCount() && !process->GetPinnedThread(core_id)) {
@@ -27,8 +29,11 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) {
process->PinCurrentThread(core_id);
// Set the interrupt flag for the thread.
- scheduler.GetCurrentThread()->SetInterruptFlag();
+ GetCurrentThread(kernel).SetInterruptFlag();
}
+
+ // Request interrupt scheduling.
+ kernel.CurrentScheduler()->RequestScheduleOnInterrupt();
}
} // namespace Kernel::KInterruptManager
diff --git a/src/core/hle/kernel/k_interrupt_manager.h b/src/core/hle/kernel/k_interrupt_manager.h
index 05924801e..f103dfe3f 100644
--- a/src/core/hle/kernel/k_interrupt_manager.h
+++ b/src/core/hle/kernel/k_interrupt_manager.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_light_condition_variable.cpp b/src/core/hle/kernel/k_light_condition_variable.cpp
index a8001fffc..cade99cfd 100644
--- a/src/core/hle/kernel/k_light_condition_variable.cpp
+++ b/src/core/hle/kernel/k_light_condition_variable.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_light_condition_variable.h"
#include "core/hle/kernel/k_scheduler.h"
@@ -18,8 +17,7 @@ public:
bool term)
: KThreadQueue(kernel_), m_wait_list(wl), m_allow_terminating_thread(term) {}
- void CancelWait(KThread* waiting_thread, ResultCode wait_result,
- bool cancel_timer_task) override {
+ void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override {
// Only process waits if we're allowed to.
if (ResultTerminationRequested == wait_result && m_allow_terminating_thread) {
return;
diff --git a/src/core/hle/kernel/k_light_condition_variable.h b/src/core/hle/kernel/k_light_condition_variable.h
index 5d6d7f128..3cabd6b4f 100644
--- a/src/core/hle/kernel/k_light_condition_variable.h
+++ b/src/core/hle/kernel/k_light_condition_variable.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_light_lock.cpp b/src/core/hle/kernel/k_light_lock.cpp
index 4620342eb..43185320d 100644
--- a/src/core/hle/kernel/k_light_lock.cpp
+++ b/src/core/hle/kernel/k_light_lock.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/k_scheduler.h"
@@ -16,8 +15,7 @@ class ThreadQueueImplForKLightLock final : public KThreadQueue {
public:
explicit ThreadQueueImplForKLightLock(KernelCore& kernel_) : KThreadQueue(kernel_) {}
- void CancelWait(KThread* waiting_thread, ResultCode wait_result,
- bool cancel_timer_task) override {
+ void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override {
// Remove the thread as a waiter from its owner.
if (KThread* owner = waiting_thread->GetLockOwner(); owner != nullptr) {
owner->RemoveWaiter(waiting_thread);
diff --git a/src/core/hle/kernel/k_light_lock.h b/src/core/hle/kernel/k_light_lock.h
index 4163b8a85..7edd950c0 100644
--- a/src/core/hle/kernel/k_light_lock.h
+++ b/src/core/hle/kernel/k_light_lock.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_linked_list.h b/src/core/hle/kernel/k_linked_list.h
index 6adfe1e34..78859ced3 100644
--- a/src/core/hle/kernel/k_linked_list.h
+++ b/src/core/hle/kernel/k_linked_list.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_memory_block.h b/src/core/hle/kernel/k_memory_block.h
index dcca47f1b..18df1f836 100644
--- a/src/core/hle/kernel/k_memory_block.h
+++ b/src/core/hle/kernel/k_memory_block.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_memory_block_manager.cpp b/src/core/hle/kernel/k_memory_block_manager.cpp
index fc7033564..3ddb9984f 100644
--- a/src/core/hle/kernel/k_memory_block_manager.cpp
+++ b/src/core/hle/kernel/k_memory_block_manager.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_memory_block_manager.h"
#include "core/hle/kernel/memory_types.h"
diff --git a/src/core/hle/kernel/k_memory_block_manager.h b/src/core/hle/kernel/k_memory_block_manager.h
index d222da919..e14741b89 100644
--- a/src/core/hle/kernel/k_memory_block_manager.h
+++ b/src/core/hle/kernel/k_memory_block_manager.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_memory_layout.board.nintendo_nx.cpp b/src/core/hle/kernel/k_memory_layout.board.nintendo_nx.cpp
index af652af58..098ba6eac 100644
--- a/src/core/hle/kernel/k_memory_layout.board.nintendo_nx.cpp
+++ b/src/core/hle/kernel/k_memory_layout.board.nintendo_nx.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
#include "common/literals.h"
diff --git a/src/core/hle/kernel/k_memory_layout.cpp b/src/core/hle/kernel/k_memory_layout.cpp
index fb1e2435f..55dc296d0 100644
--- a/src/core/hle/kernel/k_memory_layout.cpp
+++ b/src/core/hle/kernel/k_memory_layout.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
diff --git a/src/core/hle/kernel/k_memory_layout.h b/src/core/hle/kernel/k_memory_layout.h
index 57ff538cc..884fc623a 100644
--- a/src/core/hle/kernel/k_memory_layout.h
+++ b/src/core/hle/kernel/k_memory_layout.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -57,11 +56,11 @@ constexpr std::size_t KernelPageTableHeapSize = GetMaximumOverheadSize(MainMemor
constexpr std::size_t KernelInitialPageHeapSize = 128_KiB;
constexpr std::size_t KernelSlabHeapDataSize = 5_MiB;
-constexpr std::size_t KernelSlabHeapGapsSize = 2_MiB - 64_KiB;
-constexpr std::size_t KernelSlabHeapSize = KernelSlabHeapDataSize + KernelSlabHeapGapsSize;
+constexpr std::size_t KernelSlabHeapGapsSizeMax = 2_MiB - 64_KiB;
+constexpr std::size_t KernelSlabHeapSize = KernelSlabHeapDataSize + KernelSlabHeapGapsSizeMax;
// NOTE: This is calculated from KThread slab counts, assuming KThread size <= 0x860.
-constexpr std::size_t KernelSlabHeapAdditionalSize = 416_KiB;
+constexpr std::size_t KernelSlabHeapAdditionalSize = 0x68000;
constexpr std::size_t KernelResourceSize =
KernelPageTableHeapSize + KernelInitialPageHeapSize + KernelSlabHeapSize;
@@ -173,6 +172,10 @@ public:
return Dereference(FindVirtualLinear(address));
}
+ const KMemoryRegion& GetPhysicalLinearRegion(PAddr address) const {
+ return Dereference(FindPhysicalLinear(address));
+ }
+
const KMemoryRegion* GetPhysicalKernelTraceBufferRegion() const {
return GetPhysicalMemoryRegionTree().FindFirstDerived(KMemoryRegionType_KernelTraceBuffer);
}
diff --git a/src/core/hle/kernel/k_memory_manager.cpp b/src/core/hle/kernel/k_memory_manager.cpp
index 1b44541b1..5b0a9963a 100644
--- a/src/core/hle/kernel/k_memory_manager.cpp
+++ b/src/core/hle/kernel/k_memory_manager.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
@@ -10,189 +9,411 @@
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/device_memory.h"
+#include "core/hle/kernel/initial_process.h"
#include "core/hle/kernel/k_memory_manager.h"
-#include "core/hle/kernel/k_page_linked_list.h"
+#include "core/hle/kernel/k_page_group.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"
namespace Kernel {
-KMemoryManager::KMemoryManager(Core::System& system_) : system{system_} {}
+namespace {
+
+constexpr KMemoryManager::Pool GetPoolFromMemoryRegionType(u32 type) {
+ if ((type | KMemoryRegionType_DramApplicationPool) == type) {
+ return KMemoryManager::Pool::Application;
+ } else if ((type | KMemoryRegionType_DramAppletPool) == type) {
+ return KMemoryManager::Pool::Applet;
+ } else if ((type | KMemoryRegionType_DramSystemPool) == type) {
+ return KMemoryManager::Pool::System;
+ } else if ((type | KMemoryRegionType_DramSystemNonSecurePool) == type) {
+ return KMemoryManager::Pool::SystemNonSecure;
+ } else {
+ ASSERT_MSG(false, "InvalidMemoryRegionType for conversion to Pool");
+ return {};
+ }
+}
-std::size_t KMemoryManager::Impl::Initialize(Pool new_pool, u64 start_address, u64 end_address) {
- const auto size{end_address - start_address};
+} // namespace
+
+KMemoryManager::KMemoryManager(Core::System& system_)
+ : system{system_}, pool_locks{
+ KLightLock{system_.Kernel()},
+ KLightLock{system_.Kernel()},
+ KLightLock{system_.Kernel()},
+ KLightLock{system_.Kernel()},
+ } {}
+
+void KMemoryManager::Initialize(VAddr management_region, size_t management_region_size) {
+
+ // Clear the management region to zero.
+ const VAddr management_region_end = management_region + management_region_size;
+
+ // Reset our manager count.
+ num_managers = 0;
+
+ // Traverse the virtual memory layout tree, initializing each manager as appropriate.
+ while (num_managers != MaxManagerCount) {
+ // Locate the region that should initialize the current manager.
+ PAddr region_address = 0;
+ size_t region_size = 0;
+ Pool region_pool = Pool::Count;
+ for (const auto& it : system.Kernel().MemoryLayout().GetPhysicalMemoryRegionTree()) {
+ // We only care about regions that we need to create managers for.
+ if (!it.IsDerivedFrom(KMemoryRegionType_DramUserPool)) {
+ continue;
+ }
- // Calculate metadata sizes
- const auto ref_count_size{(size / PageSize) * sizeof(u16)};
- const auto optimize_map_size{(Common::AlignUp((size / PageSize), 64) / 64) * sizeof(u64)};
- const auto manager_size{Common::AlignUp(optimize_map_size + ref_count_size, PageSize)};
- const auto page_heap_size{KPageHeap::CalculateManagementOverheadSize(size)};
- const auto total_metadata_size{manager_size + page_heap_size};
- ASSERT(manager_size <= total_metadata_size);
- ASSERT(Common::IsAligned(total_metadata_size, PageSize));
+ // We want to initialize the managers in order.
+ if (it.GetAttributes() != num_managers) {
+ continue;
+ }
- // Setup region
- pool = new_pool;
+ const PAddr cur_start = it.GetAddress();
+ const PAddr cur_end = it.GetEndAddress();
+
+ // Validate the region.
+ ASSERT(cur_end != 0);
+ ASSERT(cur_start != 0);
+ ASSERT(it.GetSize() > 0);
+
+ // Update the region's extents.
+ if (region_address == 0) {
+ region_address = cur_start;
+ region_size = it.GetSize();
+ region_pool = GetPoolFromMemoryRegionType(it.GetType());
+ } else {
+ ASSERT(cur_start == region_address + region_size);
+
+ // Update the size.
+ region_size = cur_end - region_address;
+ ASSERT(GetPoolFromMemoryRegionType(it.GetType()) == region_pool);
+ }
+ }
+
+ // If we didn't find a region, we're done.
+ if (region_size == 0) {
+ break;
+ }
- // Initialize the manager's KPageHeap
- heap.Initialize(start_address, size, page_heap_size);
+ // Initialize a new manager for the region.
+ Impl* manager = std::addressof(managers[num_managers++]);
+ ASSERT(num_managers <= managers.size());
+
+ const size_t cur_size = manager->Initialize(region_address, region_size, management_region,
+ management_region_end, region_pool);
+ management_region += cur_size;
+ ASSERT(management_region <= management_region_end);
+
+ // Insert the manager into the pool list.
+ const auto region_pool_index = static_cast<u32>(region_pool);
+ if (pool_managers_tail[region_pool_index] == nullptr) {
+ pool_managers_head[region_pool_index] = manager;
+ } else {
+ pool_managers_tail[region_pool_index]->SetNext(manager);
+ manager->SetPrev(pool_managers_tail[region_pool_index]);
+ }
+ pool_managers_tail[region_pool_index] = manager;
+ }
- // Free the memory to the heap
- heap.Free(start_address, size / PageSize);
+ // Free each region to its corresponding heap.
+ size_t reserved_sizes[MaxManagerCount] = {};
+ const PAddr ini_start = GetInitialProcessBinaryPhysicalAddress();
+ const PAddr ini_end = ini_start + InitialProcessBinarySizeMax;
+ const PAddr ini_last = ini_end - 1;
+ for (const auto& it : system.Kernel().MemoryLayout().GetPhysicalMemoryRegionTree()) {
+ if (it.IsDerivedFrom(KMemoryRegionType_DramUserPool)) {
+ // Get the manager for the region.
+ auto index = it.GetAttributes();
+ auto& manager = managers[index];
+
+ const PAddr cur_start = it.GetAddress();
+ const PAddr cur_last = it.GetLastAddress();
+ const PAddr cur_end = it.GetEndAddress();
+
+ if (cur_start <= ini_start && ini_last <= cur_last) {
+ // Free memory before the ini to the heap.
+ if (cur_start != ini_start) {
+ manager.Free(cur_start, (ini_start - cur_start) / PageSize);
+ }
- // Update the heap's used size
- heap.UpdateUsedSize();
+ // Open/reserve the ini memory.
+ manager.OpenFirst(ini_start, InitialProcessBinarySizeMax / PageSize);
+ reserved_sizes[it.GetAttributes()] += InitialProcessBinarySizeMax;
- return total_metadata_size;
-}
+ // Free memory after the ini to the heap.
+ if (ini_last != cur_last) {
+ ASSERT(cur_end != 0);
+ manager.Free(ini_end, cur_end - ini_end);
+ }
+ } else {
+ // Ensure there's no partial overlap with the ini image.
+ if (cur_start <= ini_last) {
+ ASSERT(cur_last < ini_start);
+ } else {
+ // Otherwise, check the region for general validity.
+ ASSERT(cur_end != 0);
+ }
-void KMemoryManager::InitializeManager(Pool pool, u64 start_address, u64 end_address) {
- ASSERT(pool < Pool::Count);
- managers[static_cast<std::size_t>(pool)].Initialize(pool, start_address, end_address);
+ // Free the memory to the heap.
+ manager.Free(cur_start, it.GetSize() / PageSize);
+ }
+ }
+ }
+
+ // Update the used size for all managers.
+ for (size_t i = 0; i < num_managers; ++i) {
+ managers[i].SetInitialUsedHeapSize(reserved_sizes[i]);
+ }
}
-VAddr KMemoryManager::AllocateAndOpenContinuous(std::size_t num_pages, std::size_t align_pages,
- u32 option) {
- // Early return if we're allocating no pages
+PAddr KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, u32 option) {
+ // Early return if we're allocating no pages.
if (num_pages == 0) {
- return {};
+ return 0;
}
- // Lock the pool that we're allocating from
+ // Lock the pool that we're allocating from.
const auto [pool, dir] = DecodeOption(option);
- const auto pool_index{static_cast<std::size_t>(pool)};
- std::lock_guard lock{pool_locks[pool_index]};
-
- // Choose a heap based on our page size request
- const s32 heap_index{KPageHeap::GetAlignedBlockIndex(num_pages, align_pages)};
-
- // Loop, trying to iterate from each block
- // TODO (bunnei): Support multiple managers
- Impl& chosen_manager{managers[pool_index]};
- VAddr allocated_block{chosen_manager.AllocateBlock(heap_index, false)};
+ KScopedLightLock lk(pool_locks[static_cast<std::size_t>(pool)]);
+
+ // Choose a heap based on our page size request.
+ const s32 heap_index = KPageHeap::GetAlignedBlockIndex(num_pages, align_pages);
+
+ // Loop, trying to iterate from each block.
+ Impl* chosen_manager = nullptr;
+ PAddr allocated_block = 0;
+ for (chosen_manager = this->GetFirstManager(pool, dir); chosen_manager != nullptr;
+ chosen_manager = this->GetNextManager(chosen_manager, dir)) {
+ allocated_block = chosen_manager->AllocateBlock(heap_index, true);
+ if (allocated_block != 0) {
+ break;
+ }
+ }
- // If we failed to allocate, quit now
- if (!allocated_block) {
- return {};
+ // If we failed to allocate, quit now.
+ if (allocated_block == 0) {
+ return 0;
}
- // If we allocated more than we need, free some
- const auto allocated_pages{KPageHeap::GetBlockNumPages(heap_index)};
+ // If we allocated more than we need, free some.
+ const size_t allocated_pages = KPageHeap::GetBlockNumPages(heap_index);
if (allocated_pages > num_pages) {
- chosen_manager.Free(allocated_block + num_pages * PageSize, allocated_pages - num_pages);
+ chosen_manager->Free(allocated_block + num_pages * PageSize, allocated_pages - num_pages);
}
+ // Open the first reference to the pages.
+ chosen_manager->OpenFirst(allocated_block, num_pages);
+
return allocated_block;
}
-ResultCode KMemoryManager::Allocate(KPageLinkedList& page_list, std::size_t num_pages, Pool pool,
- Direction dir, u32 heap_fill_value) {
- ASSERT(page_list.GetNumPages() == 0);
+Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages, Pool pool,
+ Direction dir, bool random) {
+ // Choose a heap based on our page size request.
+ const s32 heap_index = KPageHeap::GetBlockIndex(num_pages);
+ R_UNLESS(0 <= heap_index, ResultOutOfMemory);
+
+ // Ensure that we don't leave anything un-freed.
+ auto group_guard = SCOPE_GUARD({
+ for (const auto& it : out->Nodes()) {
+ auto& manager = this->GetManager(system.Kernel().MemoryLayout(), it.GetAddress());
+ const size_t num_pages_to_free =
+ std::min(it.GetNumPages(), (manager.GetEndAddress() - it.GetAddress()) / PageSize);
+ manager.Free(it.GetAddress(), num_pages_to_free);
+ }
+ });
- // Early return if we're allocating no pages
- if (num_pages == 0) {
- return ResultSuccess;
- }
+ // Keep allocating until we've allocated all our pages.
+ for (s32 index = heap_index; index >= 0 && num_pages > 0; index--) {
+ const size_t pages_per_alloc = KPageHeap::GetBlockNumPages(index);
+ for (Impl* cur_manager = this->GetFirstManager(pool, dir); cur_manager != nullptr;
+ cur_manager = this->GetNextManager(cur_manager, dir)) {
+ while (num_pages >= pages_per_alloc) {
+ // Allocate a block.
+ PAddr allocated_block = cur_manager->AllocateBlock(index, random);
+ if (allocated_block == 0) {
+ break;
+ }
- // Lock the pool that we're allocating from
- const auto pool_index{static_cast<std::size_t>(pool)};
- std::lock_guard lock{pool_locks[pool_index]};
+ // Safely add it to our group.
+ {
+ auto block_guard =
+ SCOPE_GUARD({ cur_manager->Free(allocated_block, pages_per_alloc); });
+ R_TRY(out->AddBlock(allocated_block, pages_per_alloc));
+ block_guard.Cancel();
+ }
- // Choose a heap based on our page size request
- const s32 heap_index{KPageHeap::GetBlockIndex(num_pages)};
- if (heap_index < 0) {
- return ResultOutOfMemory;
+ num_pages -= pages_per_alloc;
+ }
+ }
}
- // TODO (bunnei): Support multiple managers
- Impl& chosen_manager{managers[pool_index]};
+ // Only succeed if we allocated as many pages as we wanted.
+ R_UNLESS(num_pages == 0, ResultOutOfMemory);
- // Ensure that we don't leave anything un-freed
- auto group_guard = detail::ScopeExit([&] {
- for (const auto& it : page_list.Nodes()) {
- const auto min_num_pages{std::min<size_t>(
- it.GetNumPages(), (chosen_manager.GetEndAddress() - it.GetAddress()) / PageSize)};
- chosen_manager.Free(it.GetAddress(), min_num_pages);
- }
- });
+ // We succeeded!
+ group_guard.Cancel();
+ return ResultSuccess;
+}
- // Keep allocating until we've allocated all our pages
- for (s32 index{heap_index}; index >= 0 && num_pages > 0; index--) {
- const auto pages_per_alloc{KPageHeap::GetBlockNumPages(index)};
+Result KMemoryManager::AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 option) {
+ ASSERT(out != nullptr);
+ ASSERT(out->GetNumPages() == 0);
- while (num_pages >= pages_per_alloc) {
- // Allocate a block
- VAddr allocated_block{chosen_manager.AllocateBlock(index, false)};
- if (!allocated_block) {
- break;
- }
+ // Early return if we're allocating no pages.
+ R_SUCCEED_IF(num_pages == 0);
- // Safely add it to our group
- {
- auto block_guard = detail::ScopeExit(
- [&] { chosen_manager.Free(allocated_block, pages_per_alloc); });
+ // Lock the pool that we're allocating from.
+ const auto [pool, dir] = DecodeOption(option);
+ KScopedLightLock lk(pool_locks[static_cast<size_t>(pool)]);
+
+ // Allocate the page group.
+ R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir, false));
+
+ // Open the first reference to the pages.
+ for (const auto& block : out->Nodes()) {
+ PAddr cur_address = block.GetAddress();
+ size_t remaining_pages = block.GetNumPages();
+ while (remaining_pages > 0) {
+ // Get the manager for the current address.
+ auto& manager = this->GetManager(system.Kernel().MemoryLayout(), cur_address);
+
+ // Process part or all of the block.
+ const size_t cur_pages =
+ std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
+ manager.OpenFirst(cur_address, cur_pages);
+
+ // Advance.
+ cur_address += cur_pages * PageSize;
+ remaining_pages -= cur_pages;
+ }
+ }
- if (const ResultCode result{page_list.AddBlock(allocated_block, pages_per_alloc)};
- result.IsError()) {
- return result;
- }
+ return ResultSuccess;
+}
- block_guard.Cancel();
- }
+Result KMemoryManager::AllocateAndOpenForProcess(KPageGroup* out, size_t num_pages, u32 option,
+ u64 process_id, u8 fill_pattern) {
+ ASSERT(out != nullptr);
+ ASSERT(out->GetNumPages() == 0);
- num_pages -= pages_per_alloc;
- }
- }
+ // Decode the option.
+ const auto [pool, dir] = DecodeOption(option);
- // Clear allocated memory.
- for (const auto& it : page_list.Nodes()) {
- std::memset(system.DeviceMemory().GetPointer(it.GetAddress()), heap_fill_value,
- it.GetSize());
+ // Allocate the memory.
+ {
+ // Lock the pool that we're allocating from.
+ KScopedLightLock lk(pool_locks[static_cast<size_t>(pool)]);
+
+ // Allocate the page group.
+ R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir, false));
+
+ // Open the first reference to the pages.
+ for (const auto& block : out->Nodes()) {
+ PAddr cur_address = block.GetAddress();
+ size_t remaining_pages = block.GetNumPages();
+ while (remaining_pages > 0) {
+ // Get the manager for the current address.
+ auto& manager = this->GetManager(system.Kernel().MemoryLayout(), cur_address);
+
+ // Process part or all of the block.
+ const size_t cur_pages =
+ std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
+ manager.OpenFirst(cur_address, cur_pages);
+
+ // Advance.
+ cur_address += cur_pages * PageSize;
+ remaining_pages -= cur_pages;
+ }
+ }
}
- // Only succeed if we allocated as many pages as we wanted
- if (num_pages) {
- return ResultOutOfMemory;
+ // Set all the allocated memory.
+ for (const auto& block : out->Nodes()) {
+ std::memset(system.DeviceMemory().GetPointer(block.GetAddress()), fill_pattern,
+ block.GetSize());
}
- // We succeeded!
- group_guard.Cancel();
-
return ResultSuccess;
}
-ResultCode KMemoryManager::Free(KPageLinkedList& page_list, std::size_t num_pages, Pool pool,
- Direction dir, u32 heap_fill_value) {
- // Early return if we're freeing no pages
- if (!num_pages) {
- return ResultSuccess;
+void KMemoryManager::Open(PAddr address, size_t num_pages) {
+ // Repeatedly open references until we've done so for all pages.
+ while (num_pages) {
+ auto& manager = this->GetManager(system.Kernel().MemoryLayout(), address);
+ const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address));
+
+ {
+ KScopedLightLock lk(pool_locks[static_cast<size_t>(manager.GetPool())]);
+ manager.Open(address, cur_pages);
+ }
+
+ num_pages -= cur_pages;
+ address += cur_pages * PageSize;
}
+}
- // Lock the pool that we're freeing from
- const auto pool_index{static_cast<std::size_t>(pool)};
- std::lock_guard lock{pool_locks[pool_index]};
+void KMemoryManager::Close(PAddr address, size_t num_pages) {
+ // Repeatedly close references until we've done so for all pages.
+ while (num_pages) {
+ auto& manager = this->GetManager(system.Kernel().MemoryLayout(), address);
+ const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address));
- // TODO (bunnei): Support multiple managers
- Impl& chosen_manager{managers[pool_index]};
+ {
+ KScopedLightLock lk(pool_locks[static_cast<size_t>(manager.GetPool())]);
+ manager.Close(address, cur_pages);
+ }
- // Free all of the pages
- for (const auto& it : page_list.Nodes()) {
- const auto min_num_pages{std::min<size_t>(
- it.GetNumPages(), (chosen_manager.GetEndAddress() - it.GetAddress()) / PageSize)};
- chosen_manager.Free(it.GetAddress(), min_num_pages);
+ num_pages -= cur_pages;
+ address += cur_pages * PageSize;
}
+}
- return ResultSuccess;
+void KMemoryManager::Close(const KPageGroup& pg) {
+ for (const auto& node : pg.Nodes()) {
+ Close(node.GetAddress(), node.GetNumPages());
+ }
+}
+void KMemoryManager::Open(const KPageGroup& pg) {
+ for (const auto& node : pg.Nodes()) {
+ Open(node.GetAddress(), node.GetNumPages());
+ }
+}
+
+size_t KMemoryManager::Impl::Initialize(PAddr address, size_t size, VAddr management,
+ VAddr management_end, Pool p) {
+ // Calculate management sizes.
+ const size_t ref_count_size = (size / PageSize) * sizeof(u16);
+ const size_t optimize_map_size = CalculateOptimizedProcessOverheadSize(size);
+ const size_t manager_size = Common::AlignUp(optimize_map_size + ref_count_size, PageSize);
+ const size_t page_heap_size = KPageHeap::CalculateManagementOverheadSize(size);
+ const size_t total_management_size = manager_size + page_heap_size;
+ ASSERT(manager_size <= total_management_size);
+ ASSERT(management + total_management_size <= management_end);
+ ASSERT(Common::IsAligned(total_management_size, PageSize));
+
+ // Setup region.
+ pool = p;
+ management_region = management;
+ page_reference_counts.resize(
+ Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize() / PageSize);
+ ASSERT(Common::IsAligned(management_region, PageSize));
+
+ // Initialize the manager's KPageHeap.
+ heap.Initialize(address, size, management + manager_size, page_heap_size);
+
+ return total_management_size;
}
-std::size_t KMemoryManager::Impl::CalculateManagementOverheadSize(std::size_t region_size) {
- const std::size_t ref_count_size = (region_size / PageSize) * sizeof(u16);
- const std::size_t optimize_map_size =
+size_t KMemoryManager::Impl::CalculateManagementOverheadSize(size_t region_size) {
+ const size_t ref_count_size = (region_size / PageSize) * sizeof(u16);
+ const size_t optimize_map_size =
(Common::AlignUp((region_size / PageSize), Common::BitSize<u64>()) /
Common::BitSize<u64>()) *
sizeof(u64);
- const std::size_t manager_meta_size =
- Common::AlignUp(optimize_map_size + ref_count_size, PageSize);
- const std::size_t page_heap_size = KPageHeap::CalculateManagementOverheadSize(region_size);
+ const size_t manager_meta_size = Common::AlignUp(optimize_map_size + ref_count_size, PageSize);
+ const size_t page_heap_size = KPageHeap::CalculateManagementOverheadSize(region_size);
return manager_meta_size + page_heap_size;
}
diff --git a/src/core/hle/kernel/k_memory_manager.h b/src/core/hle/kernel/k_memory_manager.h
index 17c7690f1..dcb9b6348 100644
--- a/src/core/hle/kernel/k_memory_manager.h
+++ b/src/core/hle/kernel/k_memory_manager.h
@@ -1,15 +1,15 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
-#include <mutex>
#include <tuple>
#include "common/common_funcs.h"
#include "common/common_types.h"
+#include "core/hle/kernel/k_light_lock.h"
+#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_page_heap.h"
#include "core/hle/result.h"
@@ -19,7 +19,7 @@ class System;
namespace Kernel {
-class KPageLinkedList;
+class KPageGroup;
class KMemoryManager final {
public:
@@ -52,22 +52,33 @@ public:
explicit KMemoryManager(Core::System& system_);
- constexpr std::size_t GetSize(Pool pool) const {
- return managers[static_cast<std::size_t>(pool)].GetSize();
+ void Initialize(VAddr management_region, size_t management_region_size);
+
+ constexpr size_t GetSize(Pool pool) const {
+ constexpr Direction GetSizeDirection = Direction::FromFront;
+ size_t total = 0;
+ for (auto* manager = this->GetFirstManager(pool, GetSizeDirection); manager != nullptr;
+ manager = this->GetNextManager(manager, GetSizeDirection)) {
+ total += manager->GetSize();
+ }
+ return total;
}
- void InitializeManager(Pool pool, u64 start_address, u64 end_address);
+ PAddr AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, u32 option);
+ Result AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 option);
+ Result AllocateAndOpenForProcess(KPageGroup* out, size_t num_pages, u32 option, u64 process_id,
+ u8 fill_pattern);
+
+ static constexpr size_t MaxManagerCount = 10;
- VAddr AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, u32 option);
- ResultCode Allocate(KPageLinkedList& page_list, std::size_t num_pages, Pool pool, Direction dir,
- u32 heap_fill_value = 0);
- ResultCode Free(KPageLinkedList& page_list, std::size_t num_pages, Pool pool, Direction dir,
- u32 heap_fill_value = 0);
+ void Close(PAddr address, size_t num_pages);
+ void Close(const KPageGroup& pg);
- static constexpr std::size_t MaxManagerCount = 10;
+ void Open(PAddr address, size_t num_pages);
+ void Open(const KPageGroup& pg);
public:
- static std::size_t CalculateManagementOverheadSize(std::size_t region_size) {
+ static size_t CalculateManagementOverheadSize(size_t region_size) {
return Impl::CalculateManagementOverheadSize(region_size);
}
@@ -100,17 +111,26 @@ private:
Impl() = default;
~Impl() = default;
- std::size_t Initialize(Pool new_pool, u64 start_address, u64 end_address);
+ size_t Initialize(PAddr address, size_t size, VAddr management, VAddr management_end,
+ Pool p);
VAddr AllocateBlock(s32 index, bool random) {
return heap.AllocateBlock(index, random);
}
- void Free(VAddr addr, std::size_t num_pages) {
+ void Free(VAddr addr, size_t num_pages) {
heap.Free(addr, num_pages);
}
- constexpr std::size_t GetSize() const {
+ void SetInitialUsedHeapSize(size_t reserved_size) {
+ heap.SetInitialUsedSize(reserved_size);
+ }
+
+ constexpr Pool GetPool() const {
+ return pool;
+ }
+
+ constexpr size_t GetSize() const {
return heap.GetSize();
}
@@ -122,10 +142,88 @@ private:
return heap.GetEndAddress();
}
- static std::size_t CalculateManagementOverheadSize(std::size_t region_size);
+ constexpr size_t GetPageOffset(PAddr address) const {
+ return heap.GetPageOffset(address);
+ }
+
+ constexpr size_t GetPageOffsetToEnd(PAddr address) const {
+ return heap.GetPageOffsetToEnd(address);
+ }
+
+ constexpr void SetNext(Impl* n) {
+ next = n;
+ }
+
+ constexpr void SetPrev(Impl* n) {
+ prev = n;
+ }
+
+ constexpr Impl* GetNext() const {
+ return next;
+ }
+
+ constexpr Impl* GetPrev() const {
+ return prev;
+ }
+
+ void OpenFirst(PAddr address, size_t num_pages) {
+ size_t index = this->GetPageOffset(address);
+ const size_t end = index + num_pages;
+ while (index < end) {
+ const RefCount ref_count = (++page_reference_counts[index]);
+ ASSERT(ref_count == 1);
- static constexpr std::size_t CalculateOptimizedProcessOverheadSize(
- std::size_t region_size) {
+ index++;
+ }
+ }
+
+ void Open(PAddr address, size_t num_pages) {
+ size_t index = this->GetPageOffset(address);
+ const size_t end = index + num_pages;
+ while (index < end) {
+ const RefCount ref_count = (++page_reference_counts[index]);
+ ASSERT(ref_count > 1);
+
+ index++;
+ }
+ }
+
+ void Close(PAddr address, size_t num_pages) {
+ size_t index = this->GetPageOffset(address);
+ const size_t end = index + num_pages;
+
+ size_t free_start = 0;
+ size_t free_count = 0;
+ while (index < end) {
+ ASSERT(page_reference_counts[index] > 0);
+ const RefCount ref_count = (--page_reference_counts[index]);
+
+ // Keep track of how many zero refcounts we see in a row, to minimize calls to free.
+ if (ref_count == 0) {
+ if (free_count > 0) {
+ free_count++;
+ } else {
+ free_start = index;
+ free_count = 1;
+ }
+ } else {
+ if (free_count > 0) {
+ this->Free(heap.GetAddress() + free_start * PageSize, free_count);
+ free_count = 0;
+ }
+ }
+
+ index++;
+ }
+
+ if (free_count > 0) {
+ this->Free(heap.GetAddress() + free_start * PageSize, free_count);
+ }
+ }
+
+ static size_t CalculateManagementOverheadSize(size_t region_size);
+
+ static constexpr size_t CalculateOptimizedProcessOverheadSize(size_t region_size) {
return (Common::AlignUp((region_size / PageSize), Common::BitSize<u64>()) /
Common::BitSize<u64>()) *
sizeof(u64);
@@ -135,13 +233,45 @@ private:
using RefCount = u16;
KPageHeap heap;
+ std::vector<RefCount> page_reference_counts;
+ VAddr management_region{};
Pool pool{};
+ Impl* next{};
+ Impl* prev{};
};
private:
+ Impl& GetManager(const KMemoryLayout& memory_layout, PAddr address) {
+ return managers[memory_layout.GetPhysicalLinearRegion(address).GetAttributes()];
+ }
+
+ const Impl& GetManager(const KMemoryLayout& memory_layout, PAddr address) const {
+ return managers[memory_layout.GetPhysicalLinearRegion(address).GetAttributes()];
+ }
+
+ constexpr Impl* GetFirstManager(Pool pool, Direction dir) const {
+ return dir == Direction::FromBack ? pool_managers_tail[static_cast<size_t>(pool)]
+ : pool_managers_head[static_cast<size_t>(pool)];
+ }
+
+ constexpr Impl* GetNextManager(Impl* cur, Direction dir) const {
+ if (dir == Direction::FromBack) {
+ return cur->GetPrev();
+ } else {
+ return cur->GetNext();
+ }
+ }
+
+ Result AllocatePageGroupImpl(KPageGroup* out, size_t num_pages, Pool pool, Direction dir,
+ bool random);
+
+private:
Core::System& system;
- std::array<std::mutex, static_cast<std::size_t>(Pool::Count)> pool_locks;
+ std::array<KLightLock, static_cast<size_t>(Pool::Count)> pool_locks;
+ std::array<Impl*, MaxManagerCount> pool_managers_head{};
+ std::array<Impl*, MaxManagerCount> pool_managers_tail{};
std::array<Impl, MaxManagerCount> managers;
+ size_t num_managers{};
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_memory_region.h b/src/core/hle/kernel/k_memory_region.h
index e9bdf4e59..5037e657f 100644
--- a/src/core/hle/kernel/k_memory_region.h
+++ b/src/core/hle/kernel/k_memory_region.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_memory_region_type.h b/src/core/hle/kernel/k_memory_region_type.h
index a05e66677..7e2fcccdc 100644
--- a/src/core/hle/kernel/k_memory_region_type.h
+++ b/src/core/hle/kernel/k_memory_region_type.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -14,7 +13,8 @@
namespace Kernel {
enum KMemoryRegionType : u32 {
- KMemoryRegionAttr_CarveoutProtected = 0x04000000,
+ KMemoryRegionAttr_CarveoutProtected = 0x02000000,
+ KMemoryRegionAttr_Uncached = 0x04000000,
KMemoryRegionAttr_DidKernelMap = 0x08000000,
KMemoryRegionAttr_ShouldKernelMap = 0x10000000,
KMemoryRegionAttr_UserReadOnly = 0x20000000,
@@ -239,6 +239,11 @@ static_assert(KMemoryRegionType_VirtualDramHeapBase.GetValue() == 0x1A);
static_assert(KMemoryRegionType_VirtualDramKernelPtHeap.GetValue() == 0x2A);
static_assert(KMemoryRegionType_VirtualDramKernelTraceBuffer.GetValue() == 0x4A);
+// UNUSED: .DeriveSparse(2, 2, 0);
+constexpr auto KMemoryRegionType_VirtualDramUnknownDebug =
+ KMemoryRegionType_Dram.DeriveSparse(2, 2, 1);
+static_assert(KMemoryRegionType_VirtualDramUnknownDebug.GetValue() == (0x52));
+
constexpr auto KMemoryRegionType_VirtualDramKernelInitPt =
KMemoryRegionType_VirtualDramHeapBase.Derive(3, 0);
constexpr auto KMemoryRegionType_VirtualDramPoolManagement =
@@ -330,6 +335,8 @@ constexpr KMemoryRegionType GetTypeForVirtualLinearMapping(u32 type_id) {
return KMemoryRegionType_VirtualDramKernelTraceBuffer;
} else if (KMemoryRegionType_DramKernelPtHeap.IsAncestorOf(type_id)) {
return KMemoryRegionType_VirtualDramKernelPtHeap;
+ } else if ((type_id | KMemoryRegionAttr_ShouldKernelMap) == type_id) {
+ return KMemoryRegionType_VirtualDramUnknownDebug;
} else {
return KMemoryRegionType_Dram;
}
diff --git a/src/core/hle/kernel/k_page_bitmap.h b/src/core/hle/kernel/k_page_bitmap.h
index c75d667c9..c97b3dc0b 100644
--- a/src/core/hle/kernel/k_page_bitmap.h
+++ b/src/core/hle/kernel/k_page_bitmap.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_page_buffer.cpp b/src/core/hle/kernel/k_page_buffer.cpp
new file mode 100644
index 000000000..1a0bf4439
--- /dev/null
+++ b/src/core/hle/kernel/k_page_buffer.cpp
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "core/core.h"
+#include "core/device_memory.h"
+#include "core/hle/kernel/k_page_buffer.h"
+#include "core/hle/kernel/memory_types.h"
+
+namespace Kernel {
+
+KPageBuffer* KPageBuffer::FromPhysicalAddress(Core::System& system, PAddr phys_addr) {
+ ASSERT(Common::IsAligned(phys_addr, PageSize));
+ return reinterpret_cast<KPageBuffer*>(system.DeviceMemory().GetPointer(phys_addr));
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_buffer.h b/src/core/hle/kernel/k_page_buffer.h
new file mode 100644
index 000000000..7e50dc1d1
--- /dev/null
+++ b/src/core/hle/kernel/k_page_buffer.h
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+#include "core/hle/kernel/memory_types.h"
+#include "core/hle/kernel/slab_helpers.h"
+
+namespace Kernel {
+
+class KPageBuffer final : public KSlabAllocated<KPageBuffer> {
+public:
+ KPageBuffer() = default;
+
+ static KPageBuffer* FromPhysicalAddress(Core::System& system, PAddr phys_addr);
+
+private:
+ [[maybe_unused]] alignas(PageSize) std::array<u8, PageSize> m_buffer{};
+};
+
+static_assert(sizeof(KPageBuffer) == PageSize);
+static_assert(alignof(KPageBuffer) == PageSize);
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_group.h b/src/core/hle/kernel/k_page_group.h
new file mode 100644
index 000000000..968753992
--- /dev/null
+++ b/src/core/hle/kernel/k_page_group.h
@@ -0,0 +1,99 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <list>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "core/hle/kernel/memory_types.h"
+#include "core/hle/result.h"
+
+namespace Kernel {
+
+class KPageGroup final {
+public:
+ class Node final {
+ public:
+ constexpr Node(u64 addr_, std::size_t num_pages_) : addr{addr_}, num_pages{num_pages_} {}
+
+ constexpr u64 GetAddress() const {
+ return addr;
+ }
+
+ constexpr std::size_t GetNumPages() const {
+ return num_pages;
+ }
+
+ constexpr std::size_t GetSize() const {
+ return GetNumPages() * PageSize;
+ }
+
+ private:
+ u64 addr{};
+ std::size_t num_pages{};
+ };
+
+public:
+ KPageGroup() = default;
+ KPageGroup(u64 address, u64 num_pages) {
+ ASSERT(AddBlock(address, num_pages).IsSuccess());
+ }
+
+ constexpr std::list<Node>& Nodes() {
+ return nodes;
+ }
+
+ constexpr const std::list<Node>& Nodes() const {
+ return nodes;
+ }
+
+ std::size_t GetNumPages() const {
+ std::size_t num_pages = 0;
+ for (const Node& node : nodes) {
+ num_pages += node.GetNumPages();
+ }
+ return num_pages;
+ }
+
+ bool IsEqual(KPageGroup& other) const {
+ auto this_node = nodes.begin();
+ auto other_node = other.nodes.begin();
+ while (this_node != nodes.end() && other_node != other.nodes.end()) {
+ if (this_node->GetAddress() != other_node->GetAddress() ||
+ this_node->GetNumPages() != other_node->GetNumPages()) {
+ return false;
+ }
+ this_node = std::next(this_node);
+ other_node = std::next(other_node);
+ }
+
+ return this_node == nodes.end() && other_node == other.nodes.end();
+ }
+
+ Result AddBlock(u64 address, u64 num_pages) {
+ if (!num_pages) {
+ return ResultSuccess;
+ }
+ if (!nodes.empty()) {
+ const auto node = nodes.back();
+ if (node.GetAddress() + node.GetNumPages() * PageSize == address) {
+ address = node.GetAddress();
+ num_pages += node.GetNumPages();
+ nodes.pop_back();
+ }
+ }
+ nodes.push_back({address, num_pages});
+ return ResultSuccess;
+ }
+
+ bool Empty() const {
+ return nodes.empty();
+ }
+
+private:
+ std::list<Node> nodes;
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_heap.cpp b/src/core/hle/kernel/k_page_heap.cpp
index 29d996d62..5ede60168 100644
--- a/src/core/hle/kernel/k_page_heap.cpp
+++ b/src/core/hle/kernel/k_page_heap.cpp
@@ -1,41 +1,56 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/hle/kernel/k_page_heap.h"
namespace Kernel {
-void KPageHeap::Initialize(VAddr address, std::size_t size, std::size_t metadata_size) {
- // Check our assumptions
- ASSERT(Common::IsAligned((address), PageSize));
+void KPageHeap::Initialize(PAddr address, size_t size, VAddr management_address,
+ size_t management_size, const size_t* block_shifts,
+ size_t num_block_shifts) {
+ // Check our assumptions.
+ ASSERT(Common::IsAligned(address, PageSize));
ASSERT(Common::IsAligned(size, PageSize));
+ ASSERT(0 < num_block_shifts && num_block_shifts <= NumMemoryBlockPageShifts);
+ const VAddr management_end = management_address + management_size;
- // Set our members
- heap_address = address;
- heap_size = size;
-
- // Setup bitmaps
- metadata.resize(metadata_size / sizeof(u64));
- u64* cur_bitmap_storage{metadata.data()};
- for (std::size_t i = 0; i < MemoryBlockPageShifts.size(); i++) {
- const std::size_t cur_block_shift{MemoryBlockPageShifts[i]};
- const std::size_t next_block_shift{
- (i != MemoryBlockPageShifts.size() - 1) ? MemoryBlockPageShifts[i + 1] : 0};
- cur_bitmap_storage = blocks[i].Initialize(heap_address, heap_size, cur_block_shift,
- next_block_shift, cur_bitmap_storage);
+ // Set our members.
+ m_heap_address = address;
+ m_heap_size = size;
+ m_num_blocks = num_block_shifts;
+
+ // Setup bitmaps.
+ m_management_data.resize(management_size / sizeof(u64));
+ u64* cur_bitmap_storage{m_management_data.data()};
+ for (size_t i = 0; i < num_block_shifts; i++) {
+ const size_t cur_block_shift = block_shifts[i];
+ const size_t next_block_shift = (i != num_block_shifts - 1) ? block_shifts[i + 1] : 0;
+ cur_bitmap_storage = m_blocks[i].Initialize(m_heap_address, m_heap_size, cur_block_shift,
+ next_block_shift, cur_bitmap_storage);
}
+
+ // Ensure we didn't overextend our bounds.
+ ASSERT(VAddr(cur_bitmap_storage) <= management_end);
+}
+
+size_t KPageHeap::GetNumFreePages() const {
+ size_t num_free = 0;
+
+ for (size_t i = 0; i < m_num_blocks; i++) {
+ num_free += m_blocks[i].GetNumFreePages();
+ }
+
+ return num_free;
}
-VAddr KPageHeap::AllocateBlock(s32 index, bool random) {
- const std::size_t needed_size{blocks[index].GetSize()};
+PAddr KPageHeap::AllocateBlock(s32 index, bool random) {
+ const size_t needed_size = m_blocks[index].GetSize();
- for (s32 i{index}; i < static_cast<s32>(MemoryBlockPageShifts.size()); i++) {
- if (const VAddr addr{blocks[i].PopBlock(random)}; addr) {
- if (const std::size_t allocated_size{blocks[i].GetSize()};
- allocated_size > needed_size) {
- Free(addr + needed_size, (allocated_size - needed_size) / PageSize);
+ for (s32 i = index; i < static_cast<s32>(m_num_blocks); i++) {
+ if (const PAddr addr = m_blocks[i].PopBlock(random); addr != 0) {
+ if (const size_t allocated_size = m_blocks[i].GetSize(); allocated_size > needed_size) {
+ this->Free(addr + needed_size, (allocated_size - needed_size) / PageSize);
}
return addr;
}
@@ -44,34 +59,34 @@ VAddr KPageHeap::AllocateBlock(s32 index, bool random) {
return 0;
}
-void KPageHeap::FreeBlock(VAddr block, s32 index) {
+void KPageHeap::FreeBlock(PAddr block, s32 index) {
do {
- block = blocks[index++].PushBlock(block);
+ block = m_blocks[index++].PushBlock(block);
} while (block != 0);
}
-void KPageHeap::Free(VAddr addr, std::size_t num_pages) {
- // Freeing no pages is a no-op
+void KPageHeap::Free(PAddr addr, size_t num_pages) {
+ // Freeing no pages is a no-op.
if (num_pages == 0) {
return;
}
- // Find the largest block size that we can free, and free as many as possible
- s32 big_index{static_cast<s32>(MemoryBlockPageShifts.size()) - 1};
- const VAddr start{addr};
- const VAddr end{(num_pages * PageSize) + addr};
- VAddr before_start{start};
- VAddr before_end{start};
- VAddr after_start{end};
- VAddr after_end{end};
+ // Find the largest block size that we can free, and free as many as possible.
+ s32 big_index = static_cast<s32>(m_num_blocks) - 1;
+ const PAddr start = addr;
+ const PAddr end = addr + num_pages * PageSize;
+ PAddr before_start = start;
+ PAddr before_end = start;
+ PAddr after_start = end;
+ PAddr after_end = end;
while (big_index >= 0) {
- const std::size_t block_size{blocks[big_index].GetSize()};
- const VAddr big_start{Common::AlignUp((start), block_size)};
- const VAddr big_end{Common::AlignDown((end), block_size)};
+ const size_t block_size = m_blocks[big_index].GetSize();
+ const PAddr big_start = Common::AlignUp(start, block_size);
+ const PAddr big_end = Common::AlignDown(end, block_size);
if (big_start < big_end) {
- // Free as many big blocks as we can
- for (auto block{big_start}; block < big_end; block += block_size) {
- FreeBlock(block, big_index);
+ // Free as many big blocks as we can.
+ for (auto block = big_start; block < big_end; block += block_size) {
+ this->FreeBlock(block, big_index);
}
before_end = big_start;
after_start = big_end;
@@ -81,31 +96,31 @@ void KPageHeap::Free(VAddr addr, std::size_t num_pages) {
}
ASSERT(big_index >= 0);
- // Free space before the big blocks
- for (s32 i{big_index - 1}; i >= 0; i--) {
- const std::size_t block_size{blocks[i].GetSize()};
+ // Free space before the big blocks.
+ for (s32 i = big_index - 1; i >= 0; i--) {
+ const size_t block_size = m_blocks[i].GetSize();
while (before_start + block_size <= before_end) {
before_end -= block_size;
- FreeBlock(before_end, i);
+ this->FreeBlock(before_end, i);
}
}
- // Free space after the big blocks
- for (s32 i{big_index - 1}; i >= 0; i--) {
- const std::size_t block_size{blocks[i].GetSize()};
+ // Free space after the big blocks.
+ for (s32 i = big_index - 1; i >= 0; i--) {
+ const size_t block_size = m_blocks[i].GetSize();
while (after_start + block_size <= after_end) {
- FreeBlock(after_start, i);
+ this->FreeBlock(after_start, i);
after_start += block_size;
}
}
}
-std::size_t KPageHeap::CalculateManagementOverheadSize(std::size_t region_size) {
- std::size_t overhead_size = 0;
- for (std::size_t i = 0; i < MemoryBlockPageShifts.size(); i++) {
- const std::size_t cur_block_shift{MemoryBlockPageShifts[i]};
- const std::size_t next_block_shift{
- (i != MemoryBlockPageShifts.size() - 1) ? MemoryBlockPageShifts[i + 1] : 0};
+size_t KPageHeap::CalculateManagementOverheadSize(size_t region_size, const size_t* block_shifts,
+ size_t num_block_shifts) {
+ size_t overhead_size = 0;
+ for (size_t i = 0; i < num_block_shifts; i++) {
+ const size_t cur_block_shift = block_shifts[i];
+ const size_t next_block_shift = (i != num_block_shifts - 1) ? block_shifts[i + 1] : 0;
overhead_size += KPageHeap::Block::CalculateManagementOverheadSize(
region_size, cur_block_shift, next_block_shift);
}
diff --git a/src/core/hle/kernel/k_page_heap.h b/src/core/hle/kernel/k_page_heap.h
index a65aa28a0..0917a8bed 100644
--- a/src/core/hle/kernel/k_page_heap.h
+++ b/src/core/hle/kernel/k_page_heap.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -23,54 +22,73 @@ public:
KPageHeap() = default;
~KPageHeap() = default;
- constexpr VAddr GetAddress() const {
- return heap_address;
+ constexpr PAddr GetAddress() const {
+ return m_heap_address;
}
- constexpr std::size_t GetSize() const {
- return heap_size;
+ constexpr size_t GetSize() const {
+ return m_heap_size;
}
- constexpr VAddr GetEndAddress() const {
- return GetAddress() + GetSize();
+ constexpr PAddr GetEndAddress() const {
+ return this->GetAddress() + this->GetSize();
}
- constexpr std::size_t GetPageOffset(VAddr block) const {
- return (block - GetAddress()) / PageSize;
+ constexpr size_t GetPageOffset(PAddr block) const {
+ return (block - this->GetAddress()) / PageSize;
+ }
+ constexpr size_t GetPageOffsetToEnd(PAddr block) const {
+ return (this->GetEndAddress() - block) / PageSize;
+ }
+
+ void Initialize(PAddr heap_address, size_t heap_size, VAddr management_address,
+ size_t management_size) {
+ return this->Initialize(heap_address, heap_size, management_address, management_size,
+ MemoryBlockPageShifts.data(), NumMemoryBlockPageShifts);
+ }
+
+ size_t GetFreeSize() const {
+ return this->GetNumFreePages() * PageSize;
}
- void Initialize(VAddr heap_address, std::size_t heap_size, std::size_t metadata_size);
- VAddr AllocateBlock(s32 index, bool random);
- void Free(VAddr addr, std::size_t num_pages);
+ void SetInitialUsedSize(size_t reserved_size) {
+ // Check that the reserved size is valid.
+ const size_t free_size = this->GetNumFreePages() * PageSize;
+ ASSERT(m_heap_size >= free_size + reserved_size);
- void UpdateUsedSize() {
- used_size = heap_size - (GetNumFreePages() * PageSize);
+ // Set the initial used size.
+ m_initial_used_size = m_heap_size - free_size - reserved_size;
}
- static std::size_t CalculateManagementOverheadSize(std::size_t region_size);
+ PAddr AllocateBlock(s32 index, bool random);
+ void Free(PAddr addr, size_t num_pages);
+
+ static size_t CalculateManagementOverheadSize(size_t region_size) {
+ return CalculateManagementOverheadSize(region_size, MemoryBlockPageShifts.data(),
+ NumMemoryBlockPageShifts);
+ }
- static constexpr s32 GetAlignedBlockIndex(std::size_t num_pages, std::size_t align_pages) {
- const auto target_pages{std::max(num_pages, align_pages)};
- for (std::size_t i = 0; i < NumMemoryBlockPageShifts; i++) {
- if (target_pages <=
- (static_cast<std::size_t>(1) << MemoryBlockPageShifts[i]) / PageSize) {
+ static constexpr s32 GetAlignedBlockIndex(size_t num_pages, size_t align_pages) {
+ const size_t target_pages = std::max(num_pages, align_pages);
+ for (size_t i = 0; i < NumMemoryBlockPageShifts; i++) {
+ if (target_pages <= (size_t(1) << MemoryBlockPageShifts[i]) / PageSize) {
return static_cast<s32>(i);
}
}
return -1;
}
- static constexpr s32 GetBlockIndex(std::size_t num_pages) {
- for (s32 i{static_cast<s32>(NumMemoryBlockPageShifts) - 1}; i >= 0; i--) {
- if (num_pages >= (static_cast<std::size_t>(1) << MemoryBlockPageShifts[i]) / PageSize) {
+ static constexpr s32 GetBlockIndex(size_t num_pages) {
+ for (s32 i = static_cast<s32>(NumMemoryBlockPageShifts) - 1; i >= 0; i--) {
+ if (num_pages >= (size_t(1) << MemoryBlockPageShifts[i]) / PageSize) {
return i;
}
}
return -1;
}
- static constexpr std::size_t GetBlockSize(std::size_t index) {
- return static_cast<std::size_t>(1) << MemoryBlockPageShifts[index];
+ static constexpr size_t GetBlockSize(size_t index) {
+ return size_t(1) << MemoryBlockPageShifts[index];
}
- static constexpr std::size_t GetBlockNumPages(std::size_t index) {
+ static constexpr size_t GetBlockNumPages(size_t index) {
return GetBlockSize(index) / PageSize;
}
@@ -83,114 +101,116 @@ private:
Block() = default;
~Block() = default;
- constexpr std::size_t GetShift() const {
- return block_shift;
+ constexpr size_t GetShift() const {
+ return m_block_shift;
}
- constexpr std::size_t GetNextShift() const {
- return next_block_shift;
+ constexpr size_t GetNextShift() const {
+ return m_next_block_shift;
}
- constexpr std::size_t GetSize() const {
- return static_cast<std::size_t>(1) << GetShift();
+ constexpr size_t GetSize() const {
+ return u64(1) << this->GetShift();
}
- constexpr std::size_t GetNumPages() const {
- return GetSize() / PageSize;
+ constexpr size_t GetNumPages() const {
+ return this->GetSize() / PageSize;
}
- constexpr std::size_t GetNumFreeBlocks() const {
- return bitmap.GetNumBits();
+ constexpr size_t GetNumFreeBlocks() const {
+ return m_bitmap.GetNumBits();
}
- constexpr std::size_t GetNumFreePages() const {
- return GetNumFreeBlocks() * GetNumPages();
+ constexpr size_t GetNumFreePages() const {
+ return this->GetNumFreeBlocks() * this->GetNumPages();
}
- u64* Initialize(VAddr addr, std::size_t size, std::size_t bs, std::size_t nbs,
- u64* bit_storage) {
- // Set shifts
- block_shift = bs;
- next_block_shift = nbs;
-
- // Align up the address
- VAddr end{addr + size};
- const auto align{(next_block_shift != 0) ? (1ULL << next_block_shift)
- : (1ULL << block_shift)};
- addr = Common::AlignDown((addr), align);
- end = Common::AlignUp((end), align);
-
- heap_address = addr;
- end_offset = (end - addr) / (1ULL << block_shift);
- return bitmap.Initialize(bit_storage, end_offset);
+ u64* Initialize(PAddr addr, size_t size, size_t bs, size_t nbs, u64* bit_storage) {
+ // Set shifts.
+ m_block_shift = bs;
+ m_next_block_shift = nbs;
+
+ // Align up the address.
+ PAddr end = addr + size;
+ const size_t align = (m_next_block_shift != 0) ? (u64(1) << m_next_block_shift)
+ : (u64(1) << m_block_shift);
+ addr = Common::AlignDown(addr, align);
+ end = Common::AlignUp(end, align);
+
+ m_heap_address = addr;
+ m_end_offset = (end - addr) / (u64(1) << m_block_shift);
+ return m_bitmap.Initialize(bit_storage, m_end_offset);
}
- VAddr PushBlock(VAddr address) {
- // Set the bit for the free block
- std::size_t offset{(address - heap_address) >> GetShift()};
- bitmap.SetBit(offset);
+ PAddr PushBlock(PAddr address) {
+ // Set the bit for the free block.
+ size_t offset = (address - m_heap_address) >> this->GetShift();
+ m_bitmap.SetBit(offset);
- // If we have a next shift, try to clear the blocks below and return the address
- if (GetNextShift()) {
- const auto diff{1ULL << (GetNextShift() - GetShift())};
+ // If we have a next shift, try to clear the blocks below this one and return the new
+ // address.
+ if (this->GetNextShift()) {
+ const size_t diff = u64(1) << (this->GetNextShift() - this->GetShift());
offset = Common::AlignDown(offset, diff);
- if (bitmap.ClearRange(offset, diff)) {
- return heap_address + (offset << GetShift());
+ if (m_bitmap.ClearRange(offset, diff)) {
+ return m_heap_address + (offset << this->GetShift());
}
}
- // We couldn't coalesce, or we're already as big as possible
- return 0;
+ // We couldn't coalesce, or we're already as big as possible.
+ return {};
}
- VAddr PopBlock(bool random) {
- // Find a free block
- const s64 soffset{bitmap.FindFreeBlock(random)};
+ PAddr PopBlock(bool random) {
+ // Find a free block.
+ s64 soffset = m_bitmap.FindFreeBlock(random);
if (soffset < 0) {
- return 0;
+ return {};
}
- const auto offset{static_cast<std::size_t>(soffset)};
+ const size_t offset = static_cast<size_t>(soffset);
- // Update our tracking and return it
- bitmap.ClearBit(offset);
- return heap_address + (offset << GetShift());
+ // Update our tracking and return it.
+ m_bitmap.ClearBit(offset);
+ return m_heap_address + (offset << this->GetShift());
}
- static constexpr std::size_t CalculateManagementOverheadSize(std::size_t region_size,
- std::size_t cur_block_shift,
- std::size_t next_block_shift) {
- const auto cur_block_size{(1ULL << cur_block_shift)};
- const auto next_block_size{(1ULL << next_block_shift)};
- const auto align{(next_block_shift != 0) ? next_block_size : cur_block_size};
+ public:
+ static constexpr size_t CalculateManagementOverheadSize(size_t region_size,
+ size_t cur_block_shift,
+ size_t next_block_shift) {
+ const size_t cur_block_size = (u64(1) << cur_block_shift);
+ const size_t next_block_size = (u64(1) << next_block_shift);
+ const size_t align = (next_block_shift != 0) ? next_block_size : cur_block_size;
return KPageBitmap::CalculateManagementOverheadSize(
(align * 2 + Common::AlignUp(region_size, align)) / cur_block_size);
}
private:
- KPageBitmap bitmap;
- VAddr heap_address{};
- uintptr_t end_offset{};
- std::size_t block_shift{};
- std::size_t next_block_shift{};
+ KPageBitmap m_bitmap;
+ PAddr m_heap_address{};
+ uintptr_t m_end_offset{};
+ size_t m_block_shift{};
+ size_t m_next_block_shift{};
};
- constexpr std::size_t GetNumFreePages() const {
- std::size_t num_free{};
-
- for (const auto& block : blocks) {
- num_free += block.GetNumFreePages();
- }
-
- return num_free;
- }
+private:
+ void Initialize(PAddr heap_address, size_t heap_size, VAddr management_address,
+ size_t management_size, const size_t* block_shifts, size_t num_block_shifts);
+ size_t GetNumFreePages() const;
- void FreeBlock(VAddr block, s32 index);
+ void FreeBlock(PAddr block, s32 index);
- static constexpr std::size_t NumMemoryBlockPageShifts{7};
- static constexpr std::array<std::size_t, NumMemoryBlockPageShifts> MemoryBlockPageShifts{
+ static constexpr size_t NumMemoryBlockPageShifts{7};
+ static constexpr std::array<size_t, NumMemoryBlockPageShifts> MemoryBlockPageShifts{
0xC, 0x10, 0x15, 0x16, 0x19, 0x1D, 0x1E,
};
- VAddr heap_address{};
- std::size_t heap_size{};
- std::size_t used_size{};
- std::array<Block, NumMemoryBlockPageShifts> blocks{};
- std::vector<u64> metadata;
+private:
+ static size_t CalculateManagementOverheadSize(size_t region_size, const size_t* block_shifts,
+ size_t num_block_shifts);
+
+private:
+ PAddr m_heap_address{};
+ size_t m_heap_size{};
+ size_t m_initial_used_size{};
+ size_t m_num_blocks{};
+ std::array<Block, NumMemoryBlockPageShifts> m_blocks{};
+ std::vector<u64> m_management_data;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_linked_list.h b/src/core/hle/kernel/k_page_linked_list.h
deleted file mode 100644
index 0e2ae582a..000000000
--- a/src/core/hle/kernel/k_page_linked_list.h
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <list>
-
-#include "common/assert.h"
-#include "common/common_types.h"
-#include "core/hle/kernel/memory_types.h"
-#include "core/hle/result.h"
-
-namespace Kernel {
-
-class KPageLinkedList final {
-public:
- class Node final {
- public:
- constexpr Node(u64 addr_, std::size_t num_pages_) : addr{addr_}, num_pages{num_pages_} {}
-
- constexpr u64 GetAddress() const {
- return addr;
- }
-
- constexpr std::size_t GetNumPages() const {
- return num_pages;
- }
-
- constexpr std::size_t GetSize() const {
- return GetNumPages() * PageSize;
- }
-
- private:
- u64 addr{};
- std::size_t num_pages{};
- };
-
-public:
- KPageLinkedList() = default;
- KPageLinkedList(u64 address, u64 num_pages) {
- ASSERT(AddBlock(address, num_pages).IsSuccess());
- }
-
- constexpr std::list<Node>& Nodes() {
- return nodes;
- }
-
- constexpr const std::list<Node>& Nodes() const {
- return nodes;
- }
-
- std::size_t GetNumPages() const {
- std::size_t num_pages = 0;
- for (const Node& node : nodes) {
- num_pages += node.GetNumPages();
- }
- return num_pages;
- }
-
- bool IsEqual(KPageLinkedList& other) const {
- auto this_node = nodes.begin();
- auto other_node = other.nodes.begin();
- while (this_node != nodes.end() && other_node != other.nodes.end()) {
- if (this_node->GetAddress() != other_node->GetAddress() ||
- this_node->GetNumPages() != other_node->GetNumPages()) {
- return false;
- }
- this_node = std::next(this_node);
- other_node = std::next(other_node);
- }
-
- return this_node == nodes.end() && other_node == other.nodes.end();
- }
-
- ResultCode AddBlock(u64 address, u64 num_pages) {
- if (!num_pages) {
- return ResultSuccess;
- }
- if (!nodes.empty()) {
- const auto node = nodes.back();
- if (node.GetAddress() + node.GetNumPages() * PageSize == address) {
- address = node.GetAddress();
- num_pages += node.GetNumPages();
- nodes.pop_back();
- }
- }
- nodes.push_back({address, num_pages});
- return ResultSuccess;
- }
-
-private:
- std::list<Node> nodes;
-};
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 912853e5c..d975de844 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
#include "common/assert.h"
@@ -10,7 +9,7 @@
#include "core/hle/kernel/k_address_space_info.h"
#include "core/hle/kernel/k_memory_block.h"
#include "core/hle/kernel/k_memory_block_manager.h"
-#include "core/hle/kernel/k_page_linked_list.h"
+#include "core/hle/kernel/k_page_group.h"
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"
@@ -36,29 +35,11 @@ constexpr std::size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceT
case FileSys::ProgramAddressSpaceType::Is39Bit:
return 39;
default:
- UNREACHABLE();
+ ASSERT(false);
return {};
}
}
-constexpr u64 GetAddressInRange(const KMemoryInfo& info, VAddr addr) {
- if (info.GetAddress() < addr) {
- return addr;
- }
- return info.GetAddress();
-}
-
-constexpr std::size_t GetSizeInRange(const KMemoryInfo& info, VAddr start, VAddr end) {
- std::size_t size{info.GetSize()};
- if (info.GetAddress() < start) {
- size -= start - info.GetAddress();
- }
- if (info.GetEndAddress() > end) {
- size -= info.GetEndAddress() - end;
- }
- return size;
-}
-
} // namespace
KPageTable::KPageTable(Core::System& system_)
@@ -66,9 +47,9 @@ KPageTable::KPageTable(Core::System& system_)
KPageTable::~KPageTable() = default;
-ResultCode KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type,
- bool enable_aslr, VAddr code_addr,
- std::size_t code_size, KMemoryManager::Pool pool) {
+Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr,
+ VAddr code_addr, std::size_t code_size,
+ KMemoryManager::Pool pool) {
const auto GetSpaceStart = [this](KAddressSpaceInfo::Type type) {
return KAddressSpaceInfo::GetAddressSpaceStart(address_space_width, type);
@@ -84,7 +65,6 @@ ResultCode KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_
std::size_t alias_region_size{GetSpaceSize(KAddressSpaceInfo::Type::Alias)};
std::size_t heap_region_size{GetSpaceSize(KAddressSpaceInfo::Type::Heap)};
- ASSERT(start <= code_addr);
ASSERT(code_addr < code_addr + code_size);
ASSERT(code_addr + code_size - 1 <= end - 1);
@@ -147,7 +127,7 @@ ResultCode KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_
const std::size_t needed_size{
(alias_region_size + heap_region_size + stack_region_size + kernel_map_region_size)};
if (alloc_size < needed_size) {
- UNREACHABLE();
+ ASSERT(false);
return ResultOutOfMemory;
}
@@ -277,8 +257,8 @@ ResultCode KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_
return InitializeMemoryLayout(start, end);
}
-ResultCode KPageTable::MapProcessCode(VAddr addr, std::size_t num_pages, KMemoryState state,
- KMemoryPermission perm) {
+Result KPageTable::MapProcessCode(VAddr addr, std::size_t num_pages, KMemoryState state,
+ KMemoryPermission perm) {
const u64 size{num_pages * PageSize};
// Validate the mapping request.
@@ -291,89 +271,367 @@ ResultCode KPageTable::MapProcessCode(VAddr addr, std::size_t num_pages, KMemory
R_TRY(this->CheckMemoryState(addr, size, KMemoryState::All, KMemoryState::Free,
KMemoryPermission::None, KMemoryPermission::None,
KMemoryAttribute::None, KMemoryAttribute::None));
+ KPageGroup pg;
+ R_TRY(system.Kernel().MemoryManager().AllocateAndOpen(
+ &pg, num_pages,
+ KMemoryManager::EncodeOption(KMemoryManager::Pool::Application, allocation_option)));
- KPageLinkedList page_linked_list;
- R_TRY(system.Kernel().MemoryManager().Allocate(page_linked_list, num_pages, memory_pool,
- allocation_option));
- R_TRY(Operate(addr, num_pages, page_linked_list, OperationType::MapGroup));
+ R_TRY(Operate(addr, num_pages, pg, OperationType::MapGroup));
block_manager->Update(addr, num_pages, state, perm);
return ResultSuccess;
}
-ResultCode KPageTable::MapCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) {
+Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, std::size_t size) {
+ // Validate the mapping request.
+ R_UNLESS(this->CanContain(dst_address, size, KMemoryState::AliasCode),
+ ResultInvalidMemoryRegion);
+
+ // Lock the table.
KScopedLightLock lk(general_lock);
- const std::size_t num_pages{size / PageSize};
+ // Verify that the source memory is normal heap.
+ KMemoryState src_state{};
+ KMemoryPermission src_perm{};
+ std::size_t num_src_allocator_blocks{};
+ R_TRY(this->CheckMemoryState(&src_state, &src_perm, nullptr, &num_src_allocator_blocks,
+ src_address, size, KMemoryState::All, KMemoryState::Normal,
+ KMemoryPermission::All, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::All, KMemoryAttribute::None));
- KMemoryState state{};
- KMemoryPermission perm{};
- CASCADE_CODE(CheckMemoryState(&state, &perm, nullptr, nullptr, src_addr, size,
- KMemoryState::All, KMemoryState::Normal, KMemoryPermission::All,
- KMemoryPermission::UserReadWrite, KMemoryAttribute::Mask,
- KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped));
+ // Verify that the destination memory is unmapped.
+ std::size_t num_dst_allocator_blocks{};
+ R_TRY(this->CheckMemoryState(&num_dst_allocator_blocks, dst_address, size, KMemoryState::All,
+ KMemoryState::Free, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryAttribute::None));
- if (IsRegionMapped(dst_addr, size)) {
- return ResultInvalidCurrentMemory;
+ // Map the code memory.
+ {
+ // Determine the number of pages being operated on.
+ const std::size_t num_pages = size / PageSize;
+
+ // Create page groups for the memory being mapped.
+ KPageGroup pg;
+ AddRegionToPages(src_address, num_pages, pg);
+
+ // Reprotect the source as kernel-read/not mapped.
+ const auto new_perm = static_cast<KMemoryPermission>(KMemoryPermission::KernelRead |
+ KMemoryPermission::NotMapped);
+ R_TRY(Operate(src_address, num_pages, new_perm, OperationType::ChangePermissions));
+
+ // Ensure that we unprotect the source pages on failure.
+ auto unprot_guard = SCOPE_GUARD({
+ ASSERT(this->Operate(src_address, num_pages, src_perm, OperationType::ChangePermissions)
+ .IsSuccess());
+ });
+
+ // Map the alias pages.
+ R_TRY(MapPages(dst_address, pg, new_perm));
+
+ // We successfully mapped the alias pages, so we don't need to unprotect the src pages on
+ // failure.
+ unprot_guard.Cancel();
+
+ // Apply the memory block updates.
+ block_manager->Update(src_address, num_pages, src_state, new_perm,
+ KMemoryAttribute::Locked);
+ block_manager->Update(dst_address, num_pages, KMemoryState::AliasCode, new_perm,
+ KMemoryAttribute::None);
}
- KPageLinkedList page_linked_list;
- AddRegionToPages(src_addr, num_pages, page_linked_list);
+ return ResultSuccess;
+}
+
+Result 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);
+
+ // Lock the table.
+ KScopedLightLock lk(general_lock);
+
+ // Verify that the source memory is locked normal heap.
+ std::size_t num_src_allocator_blocks{};
+ R_TRY(this->CheckMemoryState(std::addressof(num_src_allocator_blocks), src_address, size,
+ KMemoryState::All, KMemoryState::Normal, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::Locked));
+
+ // Verify that the destination memory is aliasable code.
+ std::size_t num_dst_allocator_blocks{};
+ R_TRY(this->CheckMemoryStateContiguous(
+ std::addressof(num_dst_allocator_blocks), dst_address, size, KMemoryState::FlagCanCodeAlias,
+ KMemoryState::FlagCanCodeAlias, KMemoryPermission::None, KMemoryPermission::None,
+ KMemoryAttribute::All, KMemoryAttribute::None));
+ // Determine whether any pages being unmapped are code.
+ bool any_code_pages = false;
{
- auto block_guard = detail::ScopeExit(
- [&] { Operate(src_addr, num_pages, perm, OperationType::ChangePermissions); });
+ KMemoryBlockManager::const_iterator it = block_manager->FindIterator(dst_address);
+ while (true) {
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // Check if the memory has code flag.
+ if ((info.GetState() & KMemoryState::FlagCode) != KMemoryState::None) {
+ any_code_pages = true;
+ break;
+ }
- CASCADE_CODE(Operate(src_addr, num_pages, KMemoryPermission::None,
- OperationType::ChangePermissions));
- CASCADE_CODE(MapPages(dst_addr, page_linked_list, KMemoryPermission::None));
+ // Check if we're done.
+ if (dst_address + size - 1 <= info.GetLastAddress()) {
+ break;
+ }
- block_guard.Cancel();
+ // Advance.
+ ++it;
+ }
}
- block_manager->Update(src_addr, num_pages, state, KMemoryPermission::None,
- KMemoryAttribute::Locked);
- block_manager->Update(dst_addr, num_pages, KMemoryState::AliasCode);
+ // Ensure that we maintain the instruction cache.
+ bool reprotected_pages = false;
+ SCOPE_EXIT({
+ if (reprotected_pages && any_code_pages) {
+ if (icache_invalidation_strategy == ICacheInvalidationStrategy::InvalidateRange) {
+ system.InvalidateCpuInstructionCacheRange(dst_address, size);
+ } else {
+ system.InvalidateCpuInstructionCaches();
+ }
+ }
+ });
+
+ // Unmap.
+ {
+ // Determine the number of pages being operated on.
+ const std::size_t num_pages = size / PageSize;
+
+ // Unmap the aliased copy of the pages.
+ R_TRY(Operate(dst_address, num_pages, KMemoryPermission::None, OperationType::Unmap));
+
+ // Try to set the permissions for the source pages back to what they should be.
+ R_TRY(Operate(src_address, num_pages, KMemoryPermission::UserReadWrite,
+ OperationType::ChangePermissions));
+
+ // Apply the memory block updates.
+ block_manager->Update(dst_address, num_pages, KMemoryState::None);
+ block_manager->Update(src_address, num_pages, KMemoryState::Normal,
+ KMemoryPermission::UserReadWrite);
+
+ // Note that we reprotected pages.
+ reprotected_pages = true;
+ }
return ResultSuccess;
}
-ResultCode KPageTable::UnmapCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) {
- KScopedLightLock lk(general_lock);
+VAddr KPageTable::FindFreeArea(VAddr region_start, std::size_t region_num_pages,
+ std::size_t num_pages, std::size_t alignment, std::size_t offset,
+ std::size_t guard_pages) {
+ VAddr address = 0;
+
+ if (num_pages <= region_num_pages) {
+ if (this->IsAslrEnabled()) {
+ // Try to directly find a free area up to 8 times.
+ for (std::size_t i = 0; i < 8; i++) {
+ const std::size_t random_offset =
+ KSystemControl::GenerateRandomRange(
+ 0, (region_num_pages - num_pages - guard_pages) * PageSize / alignment) *
+ alignment;
+ const VAddr candidate =
+ Common::AlignDown((region_start + random_offset), alignment) + offset;
+
+ KMemoryInfo info = this->QueryInfoImpl(candidate);
+
+ if (info.state != KMemoryState::Free) {
+ continue;
+ }
+ if (region_start > candidate) {
+ continue;
+ }
+ if (info.GetAddress() + guard_pages * PageSize > candidate) {
+ continue;
+ }
+
+ const VAddr candidate_end = candidate + (num_pages + guard_pages) * PageSize - 1;
+ if (candidate_end > info.GetLastAddress()) {
+ continue;
+ }
+ if (candidate_end > region_start + region_num_pages * PageSize - 1) {
+ continue;
+ }
+
+ address = candidate;
+ break;
+ }
+ // Fall back to finding the first free area with a random offset.
+ if (address == 0) {
+ // NOTE: Nintendo does not account for guard pages here.
+ // This may theoretically cause an offset to be chosen that cannot be mapped. We
+ // will account for guard pages.
+ const std::size_t offset_pages = KSystemControl::GenerateRandomRange(
+ 0, region_num_pages - num_pages - guard_pages);
+ address = block_manager->FindFreeArea(region_start + offset_pages * PageSize,
+ region_num_pages - offset_pages, num_pages,
+ alignment, offset, guard_pages);
+ }
+ }
- if (!size) {
- return ResultSuccess;
+ // Find the first free area.
+ if (address == 0) {
+ address = block_manager->FindFreeArea(region_start, region_num_pages, num_pages,
+ alignment, offset, guard_pages);
+ }
}
- const std::size_t num_pages{size / PageSize};
+ return address;
+}
- CASCADE_CODE(CheckMemoryState(nullptr, nullptr, nullptr, nullptr, src_addr, size,
- KMemoryState::All, KMemoryState::Normal, KMemoryPermission::None,
- KMemoryPermission::None, KMemoryAttribute::Mask,
- KMemoryAttribute::Locked, KMemoryAttribute::IpcAndDeviceMapped));
+Result KPageTable::MakePageGroup(KPageGroup& pg, VAddr addr, size_t num_pages) {
+ ASSERT(this->IsLockedByCurrentThread());
- KMemoryState state{};
- CASCADE_CODE(CheckMemoryState(
- &state, nullptr, nullptr, nullptr, dst_addr, PageSize, KMemoryState::FlagCanCodeAlias,
- KMemoryState::FlagCanCodeAlias, KMemoryPermission::None, KMemoryPermission::None,
- KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped));
- CASCADE_CODE(CheckMemoryState(dst_addr, size, KMemoryState::All, state, KMemoryPermission::None,
- KMemoryPermission::None, KMemoryAttribute::Mask,
- KMemoryAttribute::None));
- CASCADE_CODE(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap));
+ const size_t size = num_pages * PageSize;
- block_manager->Update(dst_addr, num_pages, KMemoryState::Free);
- block_manager->Update(src_addr, num_pages, KMemoryState::Normal,
- KMemoryPermission::UserReadWrite);
+ // 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);
- system.InvalidateCpuInstructionCacheRange(dst_addr, size);
+ // 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) {
+bool KPageTable::IsValidPageGroup(const KPageGroup& pg_ll, VAddr addr, size_t num_pages) {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ const size_t size = num_pages * PageSize;
+ const auto& pg = pg_ll.Nodes();
+ const auto& memory_layout = system.Kernel().MemoryLayout();
+
+ // Empty groups are necessarily invalid.
+ if (pg.empty()) {
+ return false;
+ }
+
+ // We're going to validate that the group we'd expect is the group we see.
+ auto cur_it = pg.begin();
+ PAddr cur_block_address = cur_it->GetAddress();
+ size_t cur_block_pages = cur_it->GetNumPages();
+
+ auto UpdateCurrentIterator = [&]() {
+ if (cur_block_pages == 0) {
+ if ((++cur_it) == pg.end()) {
+ return false;
+ }
+
+ cur_block_address = cur_it->GetAddress();
+ cur_block_pages = cur_it->GetNumPages();
+ }
+ return true;
+ };
+
+ // Begin traversal.
+ Common::PageTable::TraversalContext context;
+ Common::PageTable::TraversalEntry next_entry;
+ if (!page_table_impl.BeginTraversal(next_entry, context, addr)) {
+ return false;
+ }
+
+ // 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, comparing expected to actual.
+ while (tot_size < size) {
+ if (!page_table_impl.ContinueTraversal(next_entry, context)) {
+ return false;
+ }
+
+ if (next_entry.phys_addr != (cur_addr + cur_size)) {
+ const size_t cur_pages = cur_size / PageSize;
+
+ if (!IsHeapPhysicalAddress(memory_layout, cur_addr)) {
+ return false;
+ }
+
+ if (!UpdateCurrentIterator()) {
+ return false;
+ }
+
+ if (cur_block_address != cur_addr || cur_block_pages < cur_pages) {
+ return false;
+ }
+
+ cur_block_address += cur_size;
+ cur_block_pages -= 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 compare the right amount for the last block.
+ if (tot_size > size) {
+ cur_size -= (tot_size - size);
+ }
+
+ if (!IsHeapPhysicalAddress(memory_layout, cur_addr)) {
+ return false;
+ }
+
+ if (!UpdateCurrentIterator()) {
+ return false;
+ }
+
+ return cur_block_address == cur_addr && cur_block_pages == (cur_size / PageSize);
+}
+
+Result KPageTable::UnmapProcessMemory(VAddr dst_addr, std::size_t size, KPageTable& src_page_table,
+ VAddr src_addr) {
KScopedLightLock lk(general_lock);
const std::size_t num_pages{size / PageSize};
@@ -397,156 +655,486 @@ 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;
}
-ResultCode KPageTable::MapPhysicalMemory(VAddr addr, std::size_t size) {
+Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) {
// Lock the physical memory lock.
KScopedLightLock map_phys_mem_lk(map_physical_memory_lock);
- // Lock the table.
- KScopedLightLock lk(general_lock);
-
- std::size_t mapped_size{};
- const VAddr end_addr{addr + size};
+ // Calculate the last address for convenience.
+ const VAddr last_address = address + size - 1;
- block_manager->IterateForRange(addr, end_addr, [&](const KMemoryInfo& info) {
- if (info.state != KMemoryState::Free) {
- mapped_size += GetSizeInRange(info, addr, end_addr);
- }
- });
+ // Define iteration variables.
+ VAddr cur_address;
+ std::size_t mapped_size;
- if (mapped_size == size) {
- return ResultSuccess;
- }
+ // The entire mapping process can be retried.
+ while (true) {
+ // Check if the memory is already mapped.
+ {
+ // Lock the table.
+ KScopedLightLock lk(general_lock);
+
+ // Iterate over the memory.
+ cur_address = address;
+ mapped_size = 0;
+
+ auto it = block_manager->FindIterator(cur_address);
+ while (true) {
+ // Check that the iterator is valid.
+ ASSERT(it != block_manager->end());
+
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // Check if we're done.
+ if (last_address <= info.GetLastAddress()) {
+ if (info.GetState() != KMemoryState::Free) {
+ mapped_size += (last_address + 1 - cur_address);
+ }
+ break;
+ }
+
+ // Track the memory if it's mapped.
+ if (info.GetState() != KMemoryState::Free) {
+ mapped_size += VAddr(info.GetEndAddress()) - cur_address;
+ }
+
+ // Advance.
+ cur_address = info.GetEndAddress();
+ ++it;
+ }
- const std::size_t remaining_size{size - mapped_size};
- const std::size_t remaining_pages{remaining_size / PageSize};
+ // If the size mapped is the size requested, we've nothing to do.
+ R_SUCCEED_IF(size == mapped_size);
+ }
- // Reserve the memory from the process resource limit.
- KScopedResourceReservation memory_reservation(
- system.Kernel().CurrentProcess()->GetResourceLimit(), LimitableResource::PhysicalMemory,
- remaining_size);
- if (!memory_reservation.Succeeded()) {
- LOG_ERROR(Kernel, "Could not reserve remaining {:X} bytes", remaining_size);
- return ResultLimitReached;
+ // Allocate and map the memory.
+ {
+ // Reserve the memory from the process resource limit.
+ KScopedResourceReservation memory_reservation(
+ system.Kernel().CurrentProcess()->GetResourceLimit(),
+ LimitableResource::PhysicalMemory, size - mapped_size);
+ R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
+
+ // Allocate pages for the new memory.
+ KPageGroup pg;
+ R_TRY(system.Kernel().MemoryManager().AllocateAndOpenForProcess(
+ &pg, (size - mapped_size) / PageSize,
+ KMemoryManager::EncodeOption(memory_pool, allocation_option), 0, 0));
+
+ // Map the memory.
+ {
+ // Lock the table.
+ KScopedLightLock lk(general_lock);
+
+ size_t num_allocator_blocks = 0;
+
+ // Verify that nobody has mapped memory since we first checked.
+ {
+ // Iterate over the memory.
+ size_t checked_mapped_size = 0;
+ cur_address = address;
+
+ auto it = block_manager->FindIterator(cur_address);
+ while (true) {
+ // Check that the iterator is valid.
+ ASSERT(it != block_manager->end());
+
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ const bool is_free = info.GetState() == KMemoryState::Free;
+ if (is_free) {
+ if (info.GetAddress() < address) {
+ ++num_allocator_blocks;
+ }
+ if (last_address < info.GetLastAddress()) {
+ ++num_allocator_blocks;
+ }
+ }
+
+ // Check if we're done.
+ if (last_address <= info.GetLastAddress()) {
+ if (!is_free) {
+ checked_mapped_size += (last_address + 1 - cur_address);
+ }
+ break;
+ }
+
+ // Track the memory if it's mapped.
+ if (!is_free) {
+ checked_mapped_size += VAddr(info.GetEndAddress()) - cur_address;
+ }
+
+ // Advance.
+ cur_address = info.GetEndAddress();
+ ++it;
+ }
+
+ // If the size now isn't what it was before, somebody mapped or unmapped
+ // concurrently. If this happened, retry.
+ if (mapped_size != checked_mapped_size) {
+ continue;
+ }
+ }
+
+ // Reset the current tracking address, and make sure we clean up on failure.
+ cur_address = address;
+ auto unmap_guard = detail::ScopeExit([&] {
+ if (cur_address > address) {
+ const VAddr last_unmap_address = cur_address - 1;
+
+ // Iterate, unmapping the pages.
+ cur_address = address;
+
+ auto it = block_manager->FindIterator(cur_address);
+ while (true) {
+ // Check that the iterator is valid.
+ ASSERT(it != block_manager->end());
+
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // If the memory state is free, we mapped it and need to unmap it.
+ if (info.GetState() == KMemoryState::Free) {
+ // Determine the range to unmap.
+ const size_t cur_pages =
+ std::min(VAddr(info.GetEndAddress()) - cur_address,
+ last_unmap_address + 1 - cur_address) /
+ PageSize;
+
+ // Unmap.
+ ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None,
+ OperationType::Unmap)
+ .IsSuccess());
+ }
+
+ // Check if we're done.
+ if (last_unmap_address <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ cur_address = info.GetEndAddress();
+ ++it;
+ }
+ }
+ });
+
+ // Iterate over the memory.
+ auto pg_it = pg.Nodes().begin();
+ PAddr pg_phys_addr = pg_it->GetAddress();
+ size_t pg_pages = pg_it->GetNumPages();
+
+ auto it = block_manager->FindIterator(cur_address);
+ while (true) {
+ // Check that the iterator is valid.
+ ASSERT(it != block_manager->end());
+
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // If it's unmapped, we need to map it.
+ if (info.GetState() == KMemoryState::Free) {
+ // Determine the range to map.
+ size_t map_pages = std::min(VAddr(info.GetEndAddress()) - cur_address,
+ last_address + 1 - cur_address) /
+ PageSize;
+
+ // While we have pages to map, map them.
+ while (map_pages > 0) {
+ // Check if we're at the end of the physical block.
+ if (pg_pages == 0) {
+ // Ensure there are more pages to map.
+ ASSERT(pg_it != pg.Nodes().end());
+
+ // Advance our physical block.
+ ++pg_it;
+ pg_phys_addr = pg_it->GetAddress();
+ pg_pages = pg_it->GetNumPages();
+ }
+
+ // Map whatever we can.
+ const size_t cur_pages = std::min(pg_pages, map_pages);
+ R_TRY(Operate(cur_address, cur_pages, KMemoryPermission::UserReadWrite,
+ OperationType::Map, pg_phys_addr));
+
+ // Advance.
+ cur_address += cur_pages * PageSize;
+ map_pages -= cur_pages;
+
+ pg_phys_addr += cur_pages * PageSize;
+ pg_pages -= cur_pages;
+ }
+ }
+
+ // Check if we're done.
+ if (last_address <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ cur_address = info.GetEndAddress();
+ ++it;
+ }
+
+ // We succeeded, so commit the memory reservation.
+ memory_reservation.Commit();
+
+ // Increase our tracked mapped size.
+ mapped_physical_memory_size += (size - mapped_size);
+
+ // Update the relevant memory blocks.
+ block_manager->Update(address, size / PageSize, KMemoryState::Free,
+ KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryState::Normal, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::None);
+
+ // Cancel our guard.
+ unmap_guard.Cancel();
+
+ return ResultSuccess;
+ }
+ }
}
+}
- KPageLinkedList page_linked_list;
+Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) {
+ // Lock the physical memory lock.
+ KScopedLightLock map_phys_mem_lk(map_physical_memory_lock);
- CASCADE_CODE(system.Kernel().MemoryManager().Allocate(page_linked_list, remaining_pages,
- memory_pool, allocation_option));
+ // Lock the table.
+ KScopedLightLock lk(general_lock);
- // We succeeded, so commit the memory reservation.
- memory_reservation.Commit();
+ // Calculate the last address for convenience.
+ const VAddr last_address = address + size - 1;
- // Map the memory.
- auto node{page_linked_list.Nodes().begin()};
- PAddr map_addr{node->GetAddress()};
- std::size_t src_num_pages{node->GetNumPages()};
- block_manager->IterateForRange(addr, end_addr, [&](const KMemoryInfo& info) {
- if (info.state != KMemoryState::Free) {
- return;
- }
+ // Define iteration variables.
+ VAddr cur_address = 0;
+ std::size_t mapped_size = 0;
+ std::size_t num_allocator_blocks = 0;
- std::size_t dst_num_pages{GetSizeInRange(info, addr, end_addr) / PageSize};
- VAddr dst_addr{GetAddressInRange(info, addr)};
+ // Check if the memory is mapped.
+ {
+ // Iterate over the memory.
+ cur_address = address;
+ mapped_size = 0;
+
+ auto it = block_manager->FindIterator(cur_address);
+ while (true) {
+ // Check that the iterator is valid.
+ ASSERT(it != block_manager->end());
+
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // Verify the memory's state.
+ const bool is_normal = info.GetState() == KMemoryState::Normal &&
+ info.GetAttribute() == KMemoryAttribute::None;
+ const bool is_free = info.GetState() == KMemoryState::Free;
+ R_UNLESS(is_normal || is_free, ResultInvalidCurrentMemory);
+
+ if (is_normal) {
+ R_UNLESS(info.GetAttribute() == KMemoryAttribute::None, ResultInvalidCurrentMemory);
+
+ if (info.GetAddress() < address) {
+ ++num_allocator_blocks;
+ }
+ if (last_address < info.GetLastAddress()) {
+ ++num_allocator_blocks;
+ }
+ }
- while (dst_num_pages) {
- if (!src_num_pages) {
- node = std::next(node);
- map_addr = node->GetAddress();
- src_num_pages = node->GetNumPages();
+ // Check if we're done.
+ if (last_address <= info.GetLastAddress()) {
+ if (is_normal) {
+ mapped_size += (last_address + 1 - cur_address);
+ }
+ break;
}
- const std::size_t num_pages{std::min(src_num_pages, dst_num_pages)};
- Operate(dst_addr, num_pages, KMemoryPermission::UserReadWrite, OperationType::Map,
- map_addr);
+ // Track the memory if it's mapped.
+ if (is_normal) {
+ mapped_size += VAddr(info.GetEndAddress()) - cur_address;
+ }
- dst_addr += num_pages * PageSize;
- map_addr += num_pages * PageSize;
- src_num_pages -= num_pages;
- dst_num_pages -= num_pages;
+ // Advance.
+ cur_address = info.GetEndAddress();
+ ++it;
}
- });
- mapped_physical_memory_size += remaining_size;
-
- const std::size_t num_pages{size / PageSize};
- block_manager->Update(addr, num_pages, KMemoryState::Free, KMemoryPermission::None,
- KMemoryAttribute::None, KMemoryState::Normal,
- KMemoryPermission::UserReadWrite, KMemoryAttribute::None);
-
- return ResultSuccess;
-}
+ // If there's nothing mapped, we've nothing to do.
+ R_SUCCEED_IF(mapped_size == 0);
+ }
-ResultCode KPageTable::UnmapPhysicalMemory(VAddr addr, std::size_t size) {
- // Lock the physical memory lock.
- KScopedLightLock map_phys_mem_lk(map_physical_memory_lock);
+ // Make a page group for the unmap region.
+ KPageGroup pg;
+ {
+ auto& impl = this->PageTableImpl();
+
+ // Begin traversal.
+ Common::PageTable::TraversalContext context;
+ Common::PageTable::TraversalEntry cur_entry = {.phys_addr = 0, .block_size = 0};
+ bool cur_valid = false;
+ Common::PageTable::TraversalEntry next_entry;
+ bool next_valid = false;
+ size_t tot_size = 0;
+
+ cur_address = address;
+ next_valid = impl.BeginTraversal(next_entry, context, cur_address);
+ next_entry.block_size =
+ (next_entry.block_size - (next_entry.phys_addr & (next_entry.block_size - 1)));
+
+ // Iterate, building the group.
+ while (true) {
+ if ((!next_valid && !cur_valid) ||
+ (next_valid && cur_valid &&
+ next_entry.phys_addr == cur_entry.phys_addr + cur_entry.block_size)) {
+ cur_entry.block_size += next_entry.block_size;
+ } else {
+ if (cur_valid) {
+ // ASSERT(IsHeapPhysicalAddress(cur_entry.phys_addr));
+ R_TRY(pg.AddBlock(cur_entry.phys_addr, cur_entry.block_size / PageSize));
+ }
+
+ // Update tracking variables.
+ tot_size += cur_entry.block_size;
+ cur_entry = next_entry;
+ cur_valid = next_valid;
+ }
- // Lock the table.
- KScopedLightLock lk(general_lock);
+ if (cur_entry.block_size + tot_size >= size) {
+ break;
+ }
- const VAddr end_addr{addr + size};
- ResultCode result{ResultSuccess};
- std::size_t mapped_size{};
+ next_valid = impl.ContinueTraversal(next_entry, context);
+ }
- // Verify that the region can be unmapped
- block_manager->IterateForRange(addr, end_addr, [&](const KMemoryInfo& info) {
- if (info.state == KMemoryState::Normal) {
- if (info.attribute != KMemoryAttribute::None) {
- result = ResultInvalidCurrentMemory;
- return;
+ // Add the last block.
+ if (cur_valid) {
+ // ASSERT(IsHeapPhysicalAddress(cur_entry.phys_addr));
+ R_TRY(pg.AddBlock(cur_entry.phys_addr, (size - tot_size) / PageSize));
+ }
+ }
+ ASSERT(pg.GetNumPages() == mapped_size / PageSize);
+
+ // Reset the current tracking address, and make sure we clean up on failure.
+ cur_address = address;
+ auto remap_guard = detail::ScopeExit([&] {
+ if (cur_address > address) {
+ const VAddr last_map_address = cur_address - 1;
+ cur_address = address;
+
+ // Iterate over the memory we unmapped.
+ auto it = block_manager->FindIterator(cur_address);
+ auto pg_it = pg.Nodes().begin();
+ PAddr pg_phys_addr = pg_it->GetAddress();
+ size_t pg_pages = pg_it->GetNumPages();
+
+ while (true) {
+ // Get the memory info for the pages we unmapped, convert to property.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // If the memory is normal, we unmapped it and need to re-map it.
+ if (info.GetState() == KMemoryState::Normal) {
+ // Determine the range to map.
+ size_t map_pages = std::min(VAddr(info.GetEndAddress()) - cur_address,
+ last_map_address + 1 - cur_address) /
+ PageSize;
+
+ // While we have pages to map, map them.
+ while (map_pages > 0) {
+ // Check if we're at the end of the physical block.
+ if (pg_pages == 0) {
+ // Ensure there are more pages to map.
+ ASSERT(pg_it != pg.Nodes().end());
+
+ // Advance our physical block.
+ ++pg_it;
+ pg_phys_addr = pg_it->GetAddress();
+ pg_pages = pg_it->GetNumPages();
+ }
+
+ // Map whatever we can.
+ const size_t cur_pages = std::min(pg_pages, map_pages);
+ ASSERT(this->Operate(cur_address, cur_pages, info.GetPermission(),
+ OperationType::Map, pg_phys_addr) == ResultSuccess);
+
+ // Advance.
+ cur_address += cur_pages * PageSize;
+ map_pages -= cur_pages;
+
+ pg_phys_addr += cur_pages * PageSize;
+ pg_pages -= cur_pages;
+ }
+ }
+
+ // Check if we're done.
+ if (last_map_address <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ ++it;
}
- mapped_size += GetSizeInRange(info, addr, end_addr);
- } else if (info.state != KMemoryState::Free) {
- result = ResultInvalidCurrentMemory;
}
});
- if (result.IsError()) {
- return result;
- }
-
- if (!mapped_size) {
- return ResultSuccess;
- }
+ // Iterate over the memory, unmapping as we go.
+ auto it = block_manager->FindIterator(cur_address);
+ while (true) {
+ // Check that the iterator is valid.
+ ASSERT(it != block_manager->end());
- // Unmap each region within the range
- KPageLinkedList page_linked_list;
- block_manager->IterateForRange(addr, end_addr, [&](const KMemoryInfo& info) {
- if (info.state == KMemoryState::Normal) {
- const std::size_t block_size{GetSizeInRange(info, addr, end_addr)};
- const std::size_t block_num_pages{block_size / PageSize};
- const VAddr block_addr{GetAddressInRange(info, addr)};
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
- AddRegionToPages(block_addr, block_size / PageSize, page_linked_list);
+ // If the memory state is normal, we need to unmap it.
+ if (info.GetState() == KMemoryState::Normal) {
+ // Determine the range to unmap.
+ const size_t cur_pages = std::min(VAddr(info.GetEndAddress()) - cur_address,
+ last_address + 1 - cur_address) /
+ PageSize;
- if (result = Operate(block_addr, block_num_pages, KMemoryPermission::None,
- OperationType::Unmap);
- result.IsError()) {
- return;
- }
+ // Unmap.
+ R_TRY(Operate(cur_address, cur_pages, KMemoryPermission::None, OperationType::Unmap));
}
- });
- if (result.IsError()) {
- return result;
- }
- const std::size_t num_pages{size / PageSize};
- system.Kernel().MemoryManager().Free(page_linked_list, num_pages, memory_pool,
- allocation_option);
+ // Check if we're done.
+ if (last_address <= info.GetLastAddress()) {
+ break;
+ }
- block_manager->Update(addr, num_pages, KMemoryState::Free);
+ // Advance.
+ cur_address = info.GetEndAddress();
+ ++it;
+ }
+ // Release the memory resource.
+ mapped_physical_memory_size -= mapped_size;
auto process{system.Kernel().CurrentProcess()};
process->GetResourceLimit()->Release(LimitableResource::PhysicalMemory, mapped_size);
- mapped_physical_memory_size -= mapped_size;
+
+ // Update memory blocks.
+ block_manager->Update(address, size / PageSize, KMemoryState::Free, KMemoryPermission::None,
+ KMemoryAttribute::None);
+
+ // TODO(bunnei): This is a workaround until the next set of changes, where we add reference
+ // counting for mapped pages. Until then, we must manually close the reference to the page
+ // group.
+ system.Kernel().MemoryManager().Close(pg);
+
+ // We succeeded.
+ remap_guard.Cancel();
return ResultSuccess;
}
-ResultCode KPageTable::MapMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) {
+Result KPageTable::MapMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) {
KScopedLightLock lk(general_lock);
KMemoryState src_state{};
@@ -559,7 +1147,7 @@ ResultCode KPageTable::MapMemory(VAddr dst_addr, VAddr src_addr, std::size_t siz
return ResultInvalidCurrentMemory;
}
- KPageLinkedList page_linked_list;
+ KPageGroup page_linked_list;
const std::size_t num_pages{size / PageSize};
AddRegionToPages(src_addr, num_pages, page_linked_list);
@@ -585,7 +1173,7 @@ ResultCode KPageTable::MapMemory(VAddr dst_addr, VAddr src_addr, std::size_t siz
return ResultSuccess;
}
-ResultCode KPageTable::UnmapMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) {
+Result KPageTable::UnmapMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) {
KScopedLightLock lk(general_lock);
KMemoryState src_state{};
@@ -600,8 +1188,8 @@ ResultCode KPageTable::UnmapMemory(VAddr dst_addr, VAddr src_addr, std::size_t s
KMemoryPermission::None, KMemoryAttribute::Mask,
KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped));
- KPageLinkedList src_pages;
- KPageLinkedList dst_pages;
+ KPageGroup src_pages;
+ KPageGroup dst_pages;
const std::size_t num_pages{size / PageSize};
AddRegionToPages(src_addr, num_pages, src_pages);
@@ -627,8 +1215,8 @@ ResultCode KPageTable::UnmapMemory(VAddr dst_addr, VAddr src_addr, std::size_t s
return ResultSuccess;
}
-ResultCode KPageTable::MapPages(VAddr addr, const KPageLinkedList& page_linked_list,
- KMemoryPermission perm) {
+Result KPageTable::MapPages(VAddr addr, const KPageGroup& page_linked_list,
+ KMemoryPermission perm) {
ASSERT(this->IsLockedByCurrentThread());
VAddr cur_addr{addr};
@@ -651,8 +1239,8 @@ ResultCode KPageTable::MapPages(VAddr addr, const KPageLinkedList& page_linked_l
return ResultSuccess;
}
-ResultCode KPageTable::MapPages(VAddr address, KPageLinkedList& page_linked_list,
- KMemoryState state, KMemoryPermission perm) {
+Result KPageTable::MapPages(VAddr address, KPageGroup& page_linked_list, KMemoryState state,
+ KMemoryPermission perm) {
// Check that the map is in range.
const std::size_t num_pages{page_linked_list.GetNumPages()};
const std::size_t size{num_pages * PageSize};
@@ -675,15 +1263,54 @@ ResultCode KPageTable::MapPages(VAddr address, KPageLinkedList& page_linked_list
return ResultSuccess;
}
-ResultCode KPageTable::UnmapPages(VAddr addr, const KPageLinkedList& page_linked_list) {
+Result KPageTable::MapPages(VAddr* out_addr, std::size_t num_pages, std::size_t alignment,
+ PAddr phys_addr, bool is_pa_valid, VAddr region_start,
+ std::size_t region_num_pages, KMemoryState state,
+ KMemoryPermission perm) {
+ ASSERT(Common::IsAligned(alignment, PageSize) && alignment >= PageSize);
+
+ // Ensure this is a valid map request.
+ R_UNLESS(this->CanContain(region_start, region_num_pages * PageSize, state),
+ ResultInvalidCurrentMemory);
+ R_UNLESS(num_pages < region_num_pages, ResultOutOfMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(general_lock);
+
+ // Find a random address to map at.
+ VAddr addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment, 0,
+ this->GetNumGuardPages());
+ R_UNLESS(addr != 0, ResultOutOfMemory);
+ ASSERT(Common::IsAligned(addr, alignment));
+ ASSERT(this->CanContain(addr, num_pages * PageSize, state));
+ ASSERT(this->CheckMemoryState(addr, num_pages * PageSize, KMemoryState::All, KMemoryState::Free,
+ KMemoryPermission::None, KMemoryPermission::None,
+ KMemoryAttribute::None, KMemoryAttribute::None)
+ .IsSuccess());
+
+ // Perform mapping operation.
+ if (is_pa_valid) {
+ R_TRY(this->Operate(addr, num_pages, perm, OperationType::Map, phys_addr));
+ } else {
+ UNIMPLEMENTED();
+ }
+
+ // Update the blocks.
+ block_manager->Update(addr, num_pages, state, perm);
+
+ // We successfully mapped the pages.
+ *out_addr = addr;
+ return ResultSuccess;
+}
+
+Result KPageTable::UnmapPages(VAddr addr, const KPageGroup& page_linked_list) {
ASSERT(this->IsLockedByCurrentThread());
VAddr cur_addr{addr};
for (const auto& node : page_linked_list.Nodes()) {
- const std::size_t num_pages{(addr - cur_addr) / PageSize};
- if (const auto result{
- Operate(addr, num_pages, KMemoryPermission::None, OperationType::Unmap)};
+ if (const auto result{Operate(cur_addr, node.GetNumPages(), KMemoryPermission::None,
+ OperationType::Unmap)};
result.IsError()) {
return result;
}
@@ -694,8 +1321,7 @@ ResultCode KPageTable::UnmapPages(VAddr addr, const KPageLinkedList& page_linked
return ResultSuccess;
}
-ResultCode KPageTable::UnmapPages(VAddr addr, KPageLinkedList& page_linked_list,
- KMemoryState state) {
+Result KPageTable::UnmapPages(VAddr addr, KPageGroup& page_linked_list, KMemoryState state) {
// Check that the unmap is in range.
const std::size_t num_pages{page_linked_list.GetNumPages()};
const std::size_t size{num_pages * PageSize};
@@ -718,8 +1344,57 @@ ResultCode KPageTable::UnmapPages(VAddr addr, KPageLinkedList& page_linked_list,
return ResultSuccess;
}
-ResultCode KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size,
- Svc::MemoryPermission svc_perm) {
+Result KPageTable::UnmapPages(VAddr address, std::size_t num_pages, KMemoryState state) {
+ // Check that the unmap is in range.
+ const std::size_t size = num_pages * PageSize;
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(general_lock);
+
+ // Check the memory state.
+ std::size_t num_allocator_blocks{};
+ R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
+ KMemoryState::All, state, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::None));
+
+ // Perform the unmap.
+ R_TRY(Operate(address, num_pages, KMemoryPermission::None, OperationType::Unmap));
+
+ // Update the blocks.
+ block_manager->Update(address, num_pages, KMemoryState::Free, KMemoryPermission::None);
+
+ return ResultSuccess;
+}
+
+Result KPageTable::MakeAndOpenPageGroup(KPageGroup* 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;
+}
+
+Result KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size,
+ Svc::MemoryPermission svc_perm) {
const size_t num_pages = size / PageSize;
// Lock the table.
@@ -753,7 +1428,7 @@ ResultCode KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size,
new_state = KMemoryState::AliasCodeData;
break;
default:
- UNREACHABLE();
+ ASSERT(false);
}
}
@@ -791,7 +1466,7 @@ KMemoryInfo KPageTable::QueryInfo(VAddr addr) {
return QueryInfoImpl(addr);
}
-ResultCode KPageTable::ReserveTransferMemory(VAddr addr, std::size_t size, KMemoryPermission perm) {
+Result KPageTable::ReserveTransferMemory(VAddr addr, std::size_t size, KMemoryPermission perm) {
KScopedLightLock lk(general_lock);
KMemoryState state{};
@@ -809,7 +1484,7 @@ ResultCode KPageTable::ReserveTransferMemory(VAddr addr, std::size_t size, KMemo
return ResultSuccess;
}
-ResultCode KPageTable::ResetTransferMemory(VAddr addr, std::size_t size) {
+Result KPageTable::ResetTransferMemory(VAddr addr, std::size_t size) {
KScopedLightLock lk(general_lock);
KMemoryState state{};
@@ -824,8 +1499,8 @@ ResultCode KPageTable::ResetTransferMemory(VAddr addr, std::size_t size) {
return ResultSuccess;
}
-ResultCode KPageTable::SetMemoryPermission(VAddr addr, std::size_t size,
- Svc::MemoryPermission svc_perm) {
+Result KPageTable::SetMemoryPermission(VAddr addr, std::size_t size,
+ Svc::MemoryPermission svc_perm) {
const size_t num_pages = size / PageSize;
// Lock the table.
@@ -852,7 +1527,7 @@ ResultCode KPageTable::SetMemoryPermission(VAddr addr, std::size_t size,
return ResultSuccess;
}
-ResultCode KPageTable::SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask, u32 attr) {
+Result KPageTable::SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask, u32 attr) {
const size_t num_pages = size / PageSize;
ASSERT((static_cast<KMemoryAttribute>(mask) | KMemoryAttribute::SetMask) ==
KMemoryAttribute::SetMask);
@@ -887,7 +1562,7 @@ ResultCode KPageTable::SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask
return ResultSuccess;
}
-ResultCode KPageTable::SetMaxHeapSize(std::size_t size) {
+Result KPageTable::SetMaxHeapSize(std::size_t size) {
// Lock the table.
KScopedLightLock lk(general_lock);
@@ -899,7 +1574,7 @@ ResultCode KPageTable::SetMaxHeapSize(std::size_t size) {
return ResultSuccess;
}
-ResultCode KPageTable::SetHeapSize(VAddr* out, std::size_t size) {
+Result KPageTable::SetHeapSize(VAddr* out, std::size_t size) {
// Lock the physical memory mutex.
KScopedLightLock map_phys_mem_lk(map_physical_memory_lock);
@@ -966,9 +1641,16 @@ ResultCode KPageTable::SetHeapSize(VAddr* out, std::size_t size) {
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
// Allocate pages for the heap extension.
- KPageLinkedList page_linked_list;
- R_TRY(system.Kernel().MemoryManager().Allocate(page_linked_list, allocation_size / PageSize,
- memory_pool, allocation_option));
+ KPageGroup pg;
+ R_TRY(system.Kernel().MemoryManager().AllocateAndOpen(
+ &pg, allocation_size / PageSize,
+ KMemoryManager::EncodeOption(memory_pool, allocation_option)));
+
+ // Clear all the newly allocated pages.
+ for (const auto& it : pg.Nodes()) {
+ std::memset(system.DeviceMemory().GetPointer(it.GetAddress()), heap_fill_value,
+ it.GetSize());
+ }
// Map the pages.
{
@@ -987,7 +1669,7 @@ ResultCode KPageTable::SetHeapSize(VAddr* out, std::size_t size) {
// Map the pages.
const auto num_pages = allocation_size / PageSize;
- R_TRY(Operate(current_heap_end, num_pages, page_linked_list, OperationType::MapGroup));
+ R_TRY(Operate(current_heap_end, num_pages, pg, OperationType::MapGroup));
// Clear all the newly allocated pages.
for (std::size_t cur_page = 0; cur_page < num_pages; ++cur_page) {
@@ -1034,9 +1716,10 @@ ResultVal<VAddr> KPageTable::AllocateAndMapMemory(std::size_t needed_num_pages,
if (is_map_only) {
R_TRY(Operate(addr, needed_num_pages, perm, OperationType::Map, map_addr));
} else {
- KPageLinkedList page_group;
- R_TRY(system.Kernel().MemoryManager().Allocate(page_group, needed_num_pages, memory_pool,
- allocation_option));
+ KPageGroup page_group;
+ R_TRY(system.Kernel().MemoryManager().AllocateAndOpenForProcess(
+ &page_group, needed_num_pages,
+ KMemoryManager::EncodeOption(memory_pool, allocation_option), 0, 0));
R_TRY(Operate(addr, needed_num_pages, page_group, OperationType::MapGroup));
}
@@ -1045,11 +1728,11 @@ ResultVal<VAddr> KPageTable::AllocateAndMapMemory(std::size_t needed_num_pages,
return addr;
}
-ResultCode KPageTable::LockForDeviceAddressSpace(VAddr addr, std::size_t size) {
+Result KPageTable::LockForDeviceAddressSpace(VAddr addr, std::size_t size) {
KScopedLightLock lk(general_lock);
KMemoryPermission perm{};
- if (const ResultCode result{CheckMemoryState(
+ if (const Result result{CheckMemoryState(
nullptr, &perm, nullptr, nullptr, addr, size, KMemoryState::FlagCanChangeAttribute,
KMemoryState::FlagCanChangeAttribute, KMemoryPermission::None, KMemoryPermission::None,
KMemoryAttribute::LockedAndIpcLocked, KMemoryAttribute::None,
@@ -1068,11 +1751,11 @@ ResultCode KPageTable::LockForDeviceAddressSpace(VAddr addr, std::size_t size) {
return ResultSuccess;
}
-ResultCode KPageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size) {
+Result KPageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size) {
KScopedLightLock lk(general_lock);
KMemoryPermission perm{};
- if (const ResultCode result{CheckMemoryState(
+ if (const Result result{CheckMemoryState(
nullptr, &perm, nullptr, nullptr, addr, size, KMemoryState::FlagCanChangeAttribute,
KMemoryState::FlagCanChangeAttribute, KMemoryPermission::None, KMemoryPermission::None,
KMemoryAttribute::LockedAndIpcLocked, KMemoryAttribute::None,
@@ -1091,61 +1774,24 @@ ResultCode KPageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size)
return ResultSuccess;
}
-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;
+Result KPageTable::LockForCodeMemory(KPageGroup* out, VAddr addr, std::size_t size) {
+ return this->LockMemoryAndOpen(
+ out, 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;
+Result KPageTable::UnlockForCodeMemory(VAddr addr, std::size_t size, const KPageGroup& pg) {
+ return this->UnlockMemory(
+ addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory,
+ KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite, KMemoryAttribute::Locked, &pg);
}
-ResultCode KPageTable::InitializeMemoryLayout(VAddr start, VAddr end) {
+Result KPageTable::InitializeMemoryLayout(VAddr start, VAddr end) {
block_manager = std::make_unique<KMemoryBlockManager>(start, end);
return ResultSuccess;
@@ -1170,13 +1816,11 @@ bool KPageTable::IsRegionContiguous(VAddr addr, u64 size) const {
}
void KPageTable::AddRegionToPages(VAddr start, std::size_t num_pages,
- KPageLinkedList& page_linked_list) {
+ KPageGroup& page_linked_list) {
VAddr addr{start};
while (addr < start + (num_pages * PageSize)) {
const PAddr paddr{GetPhysicalAddr(addr)};
- if (!paddr) {
- UNREACHABLE();
- }
+ ASSERT(paddr != 0);
page_linked_list.AddBlock(paddr, 1);
addr += PageSize;
}
@@ -1191,8 +1835,8 @@ VAddr KPageTable::AllocateVirtualMemory(VAddr start, std::size_t region_num_page
IsKernel() ? 1 : 4);
}
-ResultCode KPageTable::Operate(VAddr addr, std::size_t num_pages, const KPageLinkedList& page_group,
- OperationType operation) {
+Result KPageTable::Operate(VAddr addr, std::size_t num_pages, const KPageGroup& page_group,
+ OperationType operation) {
ASSERT(this->IsLockedByCurrentThread());
ASSERT(Common::IsAligned(addr, PageSize));
@@ -1207,7 +1851,7 @@ ResultCode KPageTable::Operate(VAddr addr, std::size_t num_pages, const KPageLin
system.Memory().MapMemoryRegion(page_table_impl, addr, size, node.GetAddress());
break;
default:
- UNREACHABLE();
+ ASSERT(false);
}
addr += size;
@@ -1216,8 +1860,8 @@ ResultCode KPageTable::Operate(VAddr addr, std::size_t num_pages, const KPageLin
return ResultSuccess;
}
-ResultCode KPageTable::Operate(VAddr addr, std::size_t num_pages, KMemoryPermission perm,
- OperationType operation, PAddr map_addr) {
+Result KPageTable::Operate(VAddr addr, std::size_t num_pages, KMemoryPermission perm,
+ OperationType operation, PAddr map_addr) {
ASSERT(this->IsLockedByCurrentThread());
ASSERT(num_pages > 0);
@@ -1238,12 +1882,12 @@ ResultCode KPageTable::Operate(VAddr addr, std::size_t num_pages, KMemoryPermiss
case OperationType::ChangePermissionsAndRefresh:
break;
default:
- UNREACHABLE();
+ ASSERT(false);
}
return ResultSuccess;
}
-constexpr VAddr KPageTable::GetRegionAddress(KMemoryState state) const {
+VAddr KPageTable::GetRegionAddress(KMemoryState state) const {
switch (state) {
case KMemoryState::Free:
case KMemoryState::Kernel:
@@ -1275,11 +1919,10 @@ constexpr VAddr KPageTable::GetRegionAddress(KMemoryState state) const {
return code_region_start;
default:
UNREACHABLE();
- return {};
}
}
-constexpr std::size_t KPageTable::GetRegionSize(KMemoryState state) const {
+std::size_t KPageTable::GetRegionSize(KMemoryState state) const {
switch (state) {
case KMemoryState::Free:
case KMemoryState::Kernel:
@@ -1311,7 +1954,6 @@ constexpr std::size_t KPageTable::GetRegionSize(KMemoryState state) const {
return code_region_end - code_region_start;
default:
UNREACHABLE();
- return {};
}
}
@@ -1361,10 +2003,10 @@ bool KPageTable::CanContain(VAddr addr, std::size_t size, KMemoryState state) co
}
}
-ResultCode KPageTable::CheckMemoryState(const KMemoryInfo& info, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr) const {
+Result KPageTable::CheckMemoryState(const KMemoryInfo& info, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask,
+ KMemoryPermission perm, KMemoryAttribute attr_mask,
+ KMemoryAttribute attr) const {
// Validate the states match expectation.
R_UNLESS((info.state & state_mask) == state, ResultInvalidCurrentMemory);
R_UNLESS((info.perm & perm_mask) == perm, ResultInvalidCurrentMemory);
@@ -1373,12 +2015,11 @@ ResultCode KPageTable::CheckMemoryState(const KMemoryInfo& info, KMemoryState st
return ResultSuccess;
}
-ResultCode KPageTable::CheckMemoryStateContiguous(std::size_t* out_blocks_needed, VAddr addr,
- std::size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm,
- KMemoryAttribute attr_mask,
- KMemoryAttribute attr) const {
+Result KPageTable::CheckMemoryStateContiguous(std::size_t* out_blocks_needed, VAddr addr,
+ std::size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask,
+ KMemoryPermission perm, KMemoryAttribute attr_mask,
+ KMemoryAttribute attr) const {
ASSERT(this->IsLockedByCurrentThread());
// Get information about the first block.
@@ -1416,12 +2057,12 @@ ResultCode KPageTable::CheckMemoryStateContiguous(std::size_t* out_blocks_needed
return ResultSuccess;
}
-ResultCode KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
- KMemoryAttribute* out_attr, std::size_t* out_blocks_needed,
- VAddr addr, std::size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr, KMemoryAttribute ignore_attr) const {
+Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
+ KMemoryAttribute* out_attr, std::size_t* out_blocks_needed,
+ VAddr addr, std::size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask,
+ KMemoryPermission perm, KMemoryAttribute attr_mask,
+ KMemoryAttribute attr, KMemoryAttribute ignore_attr) const {
ASSERT(this->IsLockedByCurrentThread());
// Get information about the first block.
@@ -1478,4 +2119,109 @@ ResultCode KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermissi
return ResultSuccess;
}
+Result KPageTable::LockMemoryAndOpen(KPageGroup* 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;
+}
+
+Result 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 KPageGroup* 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) {
+ R_UNLESS(this->IsValidPageGroup(*pg, addr, num_pages), ResultInvalidMemoryRegion);
+ }
+
+ // 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 c98887d34..25774f232 100644
--- a/src/core/hle/kernel/k_page_table.h
+++ b/src/core/hle/kernel/k_page_table.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -12,6 +11,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,45 +25,57 @@ class KMemoryBlockManager;
class KPageTable final {
public:
+ enum class ICacheInvalidationStrategy : u32 { InvalidateRange, InvalidateAll };
+
YUZU_NON_COPYABLE(KPageTable);
YUZU_NON_MOVEABLE(KPageTable);
explicit KPageTable(Core::System& system_);
~KPageTable();
- ResultCode InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr,
- VAddr code_addr, std::size_t code_size,
- KMemoryManager::Pool pool);
- ResultCode MapProcessCode(VAddr addr, std::size_t pages_count, KMemoryState state,
- KMemoryPermission perm);
- ResultCode MapCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
- ResultCode UnmapCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
- ResultCode UnmapProcessMemory(VAddr dst_addr, std::size_t size, KPageTable& src_page_table,
- VAddr src_addr);
- ResultCode MapPhysicalMemory(VAddr addr, std::size_t size);
- ResultCode UnmapPhysicalMemory(VAddr addr, std::size_t size);
- ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
- ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
- ResultCode MapPages(VAddr addr, KPageLinkedList& page_linked_list, KMemoryState state,
- KMemoryPermission perm);
- ResultCode UnmapPages(VAddr addr, KPageLinkedList& page_linked_list, KMemoryState state);
- ResultCode SetProcessMemoryPermission(VAddr addr, std::size_t size,
- Svc::MemoryPermission svc_perm);
+ Result InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr,
+ VAddr code_addr, std::size_t code_size, KMemoryManager::Pool pool);
+ Result MapProcessCode(VAddr addr, std::size_t pages_count, KMemoryState state,
+ KMemoryPermission perm);
+ Result MapCodeMemory(VAddr dst_address, VAddr src_address, std::size_t size);
+ Result UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::size_t size,
+ ICacheInvalidationStrategy icache_invalidation_strategy);
+ Result UnmapProcessMemory(VAddr dst_addr, std::size_t size, KPageTable& src_page_table,
+ VAddr src_addr);
+ Result MapPhysicalMemory(VAddr addr, std::size_t size);
+ Result UnmapPhysicalMemory(VAddr addr, std::size_t size);
+ Result MapMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
+ Result UnmapMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
+ Result MapPages(VAddr addr, KPageGroup& page_linked_list, KMemoryState state,
+ KMemoryPermission perm);
+ Result MapPages(VAddr* out_addr, std::size_t num_pages, std::size_t alignment, PAddr phys_addr,
+ KMemoryState state, KMemoryPermission perm) {
+ return this->MapPages(out_addr, num_pages, alignment, phys_addr, true,
+ this->GetRegionAddress(state), this->GetRegionSize(state) / PageSize,
+ state, perm);
+ }
+ Result UnmapPages(VAddr addr, KPageGroup& page_linked_list, KMemoryState state);
+ Result UnmapPages(VAddr address, std::size_t num_pages, KMemoryState state);
+ Result SetProcessMemoryPermission(VAddr addr, std::size_t size, Svc::MemoryPermission svc_perm);
KMemoryInfo QueryInfo(VAddr addr);
- ResultCode ReserveTransferMemory(VAddr addr, std::size_t size, KMemoryPermission perm);
- ResultCode ResetTransferMemory(VAddr addr, std::size_t size);
- ResultCode SetMemoryPermission(VAddr addr, std::size_t size, Svc::MemoryPermission perm);
- ResultCode SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask, u32 attr);
- ResultCode SetMaxHeapSize(std::size_t size);
- ResultCode SetHeapSize(VAddr* out, std::size_t size);
+ Result ReserveTransferMemory(VAddr addr, std::size_t size, KMemoryPermission perm);
+ Result ResetTransferMemory(VAddr addr, std::size_t size);
+ Result SetMemoryPermission(VAddr addr, std::size_t size, Svc::MemoryPermission perm);
+ Result SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask, u32 attr);
+ Result SetMaxHeapSize(std::size_t size);
+ Result SetHeapSize(VAddr* out, std::size_t size);
ResultVal<VAddr> AllocateAndMapMemory(std::size_t needed_num_pages, std::size_t align,
bool is_map_only, VAddr region_start,
std::size_t region_num_pages, KMemoryState state,
KMemoryPermission perm, PAddr map_addr = 0);
- ResultCode LockForDeviceAddressSpace(VAddr addr, std::size_t size);
- ResultCode UnlockForDeviceAddressSpace(VAddr addr, std::size_t size);
- ResultCode LockForCodeMemory(VAddr addr, std::size_t size);
- ResultCode UnlockForCodeMemory(VAddr addr, std::size_t size);
+ Result LockForDeviceAddressSpace(VAddr addr, std::size_t size);
+ Result UnlockForDeviceAddressSpace(VAddr addr, std::size_t size);
+ Result LockForCodeMemory(KPageGroup* out, VAddr addr, std::size_t size);
+ Result UnlockForCodeMemory(VAddr addr, std::size_t size, const KPageGroup& pg);
+ Result MakeAndOpenPageGroup(KPageGroup* 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;
@@ -88,68 +100,97 @@ private:
KMemoryAttribute::IpcLocked |
KMemoryAttribute::DeviceShared;
- ResultCode InitializeMemoryLayout(VAddr start, VAddr end);
- ResultCode MapPages(VAddr addr, const KPageLinkedList& page_linked_list,
- KMemoryPermission perm);
- ResultCode UnmapPages(VAddr addr, const KPageLinkedList& page_linked_list);
+ Result InitializeMemoryLayout(VAddr start, VAddr end);
+ Result MapPages(VAddr addr, const KPageGroup& page_linked_list, KMemoryPermission perm);
+ Result MapPages(VAddr* out_addr, std::size_t num_pages, std::size_t alignment, PAddr phys_addr,
+ bool is_pa_valid, VAddr region_start, std::size_t region_num_pages,
+ KMemoryState state, KMemoryPermission perm);
+ Result UnmapPages(VAddr addr, const KPageGroup& page_linked_list);
bool IsRegionMapped(VAddr address, u64 size);
bool IsRegionContiguous(VAddr addr, u64 size) const;
- void AddRegionToPages(VAddr start, std::size_t num_pages, KPageLinkedList& page_linked_list);
+ void AddRegionToPages(VAddr start, std::size_t num_pages, KPageGroup& page_linked_list);
KMemoryInfo QueryInfoImpl(VAddr addr);
VAddr AllocateVirtualMemory(VAddr start, std::size_t region_num_pages, u64 needed_num_pages,
std::size_t align);
- ResultCode Operate(VAddr addr, std::size_t num_pages, const KPageLinkedList& page_group,
- OperationType operation);
- ResultCode Operate(VAddr addr, std::size_t num_pages, KMemoryPermission perm,
- OperationType operation, PAddr map_addr = 0);
- constexpr VAddr GetRegionAddress(KMemoryState state) const;
- constexpr std::size_t GetRegionSize(KMemoryState state) const;
-
- ResultCode CheckMemoryStateContiguous(std::size_t* out_blocks_needed, VAddr addr,
- std::size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr) const;
- ResultCode CheckMemoryStateContiguous(VAddr addr, std::size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr) const {
+ Result Operate(VAddr addr, std::size_t num_pages, const KPageGroup& page_group,
+ OperationType operation);
+ Result Operate(VAddr addr, std::size_t num_pages, KMemoryPermission perm,
+ OperationType operation, PAddr map_addr = 0);
+ VAddr GetRegionAddress(KMemoryState state) const;
+ std::size_t GetRegionSize(KMemoryState state) const;
+
+ VAddr FindFreeArea(VAddr region_start, std::size_t region_num_pages, std::size_t num_pages,
+ std::size_t alignment, std::size_t offset, std::size_t guard_pages);
+
+ Result CheckMemoryStateContiguous(std::size_t* out_blocks_needed, VAddr addr, std::size_t size,
+ KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr) const;
+ Result CheckMemoryStateContiguous(VAddr addr, std::size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask,
+ KMemoryPermission perm, KMemoryAttribute attr_mask,
+ KMemoryAttribute attr) const {
return this->CheckMemoryStateContiguous(nullptr, addr, size, state_mask, state, perm_mask,
perm, attr_mask, attr);
}
- ResultCode CheckMemoryState(const KMemoryInfo& info, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr) const;
- ResultCode CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
- KMemoryAttribute* out_attr, std::size_t* out_blocks_needed,
- VAddr addr, std::size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr,
- KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const;
- ResultCode CheckMemoryState(std::size_t* out_blocks_needed, VAddr addr, std::size_t size,
- KMemoryState state_mask, KMemoryState state,
- KMemoryPermission perm_mask, KMemoryPermission perm,
- KMemoryAttribute attr_mask, KMemoryAttribute attr,
- KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const {
+ Result CheckMemoryState(const KMemoryInfo& info, KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr) const;
+ Result CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
+ KMemoryAttribute* out_attr, std::size_t* out_blocks_needed, VAddr addr,
+ std::size_t size, KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr,
+ KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const;
+ Result CheckMemoryState(std::size_t* out_blocks_needed, VAddr addr, std::size_t size,
+ KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr,
+ KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const {
return CheckMemoryState(nullptr, nullptr, nullptr, out_blocks_needed, addr, size,
state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr);
}
- ResultCode CheckMemoryState(VAddr addr, size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr,
- KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const {
+ Result CheckMemoryState(VAddr addr, std::size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr,
+ KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const {
return this->CheckMemoryState(nullptr, addr, size, state_mask, state, perm_mask, perm,
attr_mask, attr, ignore_attr);
}
+ Result LockMemoryAndOpen(KPageGroup* 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);
+ Result 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 KPageGroup* pg);
+
+ Result MakePageGroup(KPageGroup& pg, VAddr addr, size_t num_pages);
+ bool IsValidPageGroup(const KPageGroup& 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;
@@ -210,7 +251,7 @@ public:
constexpr VAddr GetAliasCodeRegionSize() const {
return alias_code_region_end - alias_code_region_start;
}
- size_t GetNormalMemorySize() {
+ std::size_t GetNormalMemorySize() {
KScopedLightLock lk(general_lock);
return GetHeapSize() + mapped_physical_memory_size;
}
@@ -253,9 +294,10 @@ public:
constexpr bool IsInsideASLRRegion(VAddr address, std::size_t size) const {
return !IsOutsideASLRRegion(address, size);
}
-
- PAddr GetPhysicalAddr(VAddr addr) {
- ASSERT(IsLockedByCurrentThread());
+ constexpr std::size_t GetNumGuardPages() const {
+ return IsKernel() ? 1 : 4;
+ }
+ PAddr GetPhysicalAddr(VAddr addr) const {
const auto backing_addr = page_table_impl.backing_addr[addr >> PageBits];
ASSERT(backing_addr);
return backing_addr + addr;
@@ -276,10 +318,6 @@ private:
return is_aslr_enabled;
}
- constexpr std::size_t GetNumGuardPages() const {
- return IsKernel() ? 1 : 4;
- }
-
constexpr bool ContainsPages(VAddr addr, std::size_t num_pages) const {
return (address_space_start <= addr) &&
(num_pages <= (address_space_end - address_space_start) / PageSize) &&
@@ -311,6 +349,9 @@ private:
bool is_kernel{};
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_port.cpp b/src/core/hle/kernel/k_port.cpp
index a8ba09c4a..7a5a9dc2a 100644
--- a/src/core/hle/kernel/k_port.cpp
+++ b/src/core/hle/kernel/k_port.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/k_port.h"
@@ -51,13 +50,18 @@ bool KPort::IsServerClosed() const {
return state == State::ServerClosed;
}
-ResultCode KPort::EnqueueSession(KServerSession* session) {
+Result KPort::EnqueueSession(KServerSession* session) {
KScopedSchedulerLock sl{kernel};
R_UNLESS(state == State::Normal, ResultPortClosed);
server.EnqueueSession(session);
- server.GetSessionRequestHandler()->ClientConnected(server.AcceptSession());
+
+ if (auto session_ptr = server.GetSessionRequestHandler().lock()) {
+ session_ptr->ClientConnected(server.AcceptSession());
+ } else {
+ ASSERT(false);
+ }
return ResultSuccess;
}
diff --git a/src/core/hle/kernel/k_port.h b/src/core/hle/kernel/k_port.h
index b6e4a1fcd..0cfc16dab 100644
--- a/src/core/hle/kernel/k_port.h
+++ b/src/core/hle/kernel/k_port.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -35,7 +34,7 @@ public:
bool IsServerClosed() const;
- ResultCode EnqueueSession(KServerSession* session);
+ Result EnqueueSession(KServerSession* session);
KClientPort& GetClientPort() {
return client;
diff --git a/src/core/hle/kernel/k_priority_queue.h b/src/core/hle/kernel/k_priority_queue.h
index bd779739d..cb2512b0b 100644
--- a/src/core/hle/kernel/k_priority_queue.h
+++ b/src/core/hle/kernel/k_priority_queue.h
@@ -1,9 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-// This file references various implementation details from Atmosphere, an open-source firmware for
-// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 85c506979..d3e99665f 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <bitset>
@@ -13,7 +12,6 @@
#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/core.h"
-#include "core/device_memory.h"
#include "core/file_sys/program_metadata.h"
#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/k_memory_block_manager.h"
@@ -24,7 +22,6 @@
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/k_shared_memory_info.h"
-#include "core/hle/kernel/k_slab_heap.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"
@@ -59,76 +56,22 @@ void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority
thread->GetContext64().cpu_registers[0] = 0;
thread->GetContext32().cpu_registers[1] = thread_handle;
thread->GetContext64().cpu_registers[1] = thread_handle;
- thread->DisableDispatch();
- auto& kernel = system.Kernel();
- // Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
- {
- KScopedSchedulerLock lock{kernel};
- thread->SetState(ThreadState::Runnable);
+ if (system.DebuggerEnabled()) {
+ thread->RequestSuspend(SuspendType::Debug);
}
+
+ // Run our thread.
+ void(thread->Run());
}
} // Anonymous namespace
-// Represents a page used for thread-local storage.
-//
-// Each TLS page contains slots that may be used by processes and threads.
-// Every process and thread is created with a slot in some arbitrary page
-// (whichever page happens to have an available slot).
-class TLSPage {
-public:
- static constexpr std::size_t num_slot_entries =
- Core::Memory::PAGE_SIZE / Core::Memory::TLS_ENTRY_SIZE;
-
- explicit TLSPage(VAddr address) : base_address{address} {}
-
- bool HasAvailableSlots() const {
- return !is_slot_used.all();
- }
-
- VAddr GetBaseAddress() const {
- return base_address;
- }
-
- std::optional<VAddr> ReserveSlot() {
- for (std::size_t i = 0; i < is_slot_used.size(); i++) {
- if (is_slot_used[i]) {
- continue;
- }
-
- is_slot_used[i] = true;
- return base_address + (i * Core::Memory::TLS_ENTRY_SIZE);
- }
-
- return std::nullopt;
- }
-
- void ReleaseSlot(VAddr address) {
- // Ensure that all given addresses are consistent with how TLS pages
- // are intended to be used when releasing slots.
- ASSERT(IsWithinPage(address));
- ASSERT((address % Core::Memory::TLS_ENTRY_SIZE) == 0);
-
- const std::size_t index = (address - base_address) / Core::Memory::TLS_ENTRY_SIZE;
- is_slot_used[index] = false;
- }
-
-private:
- bool IsWithinPage(VAddr address) const {
- return base_address <= address && address < base_address + Core::Memory::PAGE_SIZE;
- }
-
- VAddr base_address;
- std::bitset<num_slot_entries> is_slot_used;
-};
-
-ResultCode KProcess::Initialize(KProcess* process, Core::System& system, std::string process_name,
- ProcessType type) {
+Result KProcess::Initialize(KProcess* process, Core::System& system, std::string process_name,
+ ProcessType type, KResourceLimit* res_limit) {
auto& kernel = system.Kernel();
process->name = std::move(process_name);
-
- process->resource_limit = kernel.GetSystemResourceLimit();
+ process->resource_limit = res_limit;
process->status = ProcessStatus::Created;
process->program_id = 0;
process->process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID()
@@ -143,9 +86,6 @@ ResultCode KProcess::Initialize(KProcess* process, Core::System& system, std::st
kernel.AppendNewProcess(process);
- // Open a reference to the resource limit.
- process->resource_limit->Open();
-
// Clear remaining fields.
process->num_running_threads = 0;
process->is_signaled = false;
@@ -153,6 +93,9 @@ ResultCode KProcess::Initialize(KProcess* process, Core::System& system, std::st
process->is_suspended = false;
process->schedule_count = 0;
+ // Open a reference to the resource limit.
+ process->resource_limit->Open();
+
return ResultSuccess;
}
@@ -217,7 +160,7 @@ bool KProcess::ReleaseUserException(KThread* thread) {
std::addressof(num_waiters),
reinterpret_cast<uintptr_t>(std::addressof(exception_thread)));
next != nullptr) {
- next->SetState(ThreadState::Runnable);
+ next->EndWait(ResultSuccess);
}
KScheduler::SetSchedulerUpdateNeeded(kernel);
@@ -232,7 +175,8 @@ void KProcess::PinCurrentThread(s32 core_id) {
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
// Get the current thread.
- KThread* cur_thread = kernel.Scheduler(static_cast<std::size_t>(core_id)).GetCurrentThread();
+ KThread* cur_thread =
+ kernel.Scheduler(static_cast<std::size_t>(core_id)).GetSchedulerCurrentThread();
// If the thread isn't terminated, pin it.
if (!cur_thread->IsTerminationRequested()) {
@@ -249,7 +193,8 @@ void KProcess::UnpinCurrentThread(s32 core_id) {
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
// Get the current thread.
- KThread* cur_thread = kernel.Scheduler(static_cast<std::size_t>(core_id)).GetCurrentThread();
+ KThread* cur_thread =
+ kernel.Scheduler(static_cast<std::size_t>(core_id)).GetSchedulerCurrentThread();
// Unpin it.
cur_thread->Unpin();
@@ -273,8 +218,8 @@ void KProcess::UnpinThread(KThread* thread) {
KScheduler::SetSchedulerUpdateNeeded(kernel);
}
-ResultCode KProcess::AddSharedMemory(KSharedMemory* shmem, [[maybe_unused]] VAddr address,
- [[maybe_unused]] size_t size) {
+Result KProcess::AddSharedMemory(KSharedMemory* shmem, [[maybe_unused]] VAddr address,
+ [[maybe_unused]] size_t size) {
// Lock ourselves, to prevent concurrent access.
KScopedLightLock lk(state_lock);
@@ -326,15 +271,19 @@ void KProcess::RemoveSharedMemory(KSharedMemory* shmem, [[maybe_unused]] VAddr a
shmem->Close();
}
-void KProcess::RegisterThread(const KThread* thread) {
+void KProcess::RegisterThread(KThread* thread) {
+ KScopedLightLock lk{list_lock};
+
thread_list.push_back(thread);
}
-void KProcess::UnregisterThread(const KThread* thread) {
+void KProcess::UnregisterThread(KThread* thread) {
+ KScopedLightLock lk{list_lock};
+
thread_list.remove(thread);
}
-ResultCode KProcess::Reset() {
+Result KProcess::Reset() {
// Lock the process and the scheduler.
KScopedLightLock lk(state_lock);
KScopedSchedulerLock sl{kernel};
@@ -348,8 +297,51 @@ ResultCode KProcess::Reset() {
return ResultSuccess;
}
-ResultCode KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata,
- std::size_t code_size) {
+Result KProcess::SetActivity(ProcessActivity activity) {
+ // Lock ourselves and the scheduler.
+ KScopedLightLock lk{state_lock};
+ KScopedLightLock list_lk{list_lock};
+ KScopedSchedulerLock sl{kernel};
+
+ // Validate our state.
+ R_UNLESS(status != ProcessStatus::Exiting, ResultInvalidState);
+ R_UNLESS(status != ProcessStatus::Exited, ResultInvalidState);
+
+ // Either pause or resume.
+ if (activity == ProcessActivity::Paused) {
+ // Verify that we're not suspended.
+ if (is_suspended) {
+ return ResultInvalidState;
+ }
+
+ // Suspend all threads.
+ for (auto* thread : GetThreadList()) {
+ thread->RequestSuspend(SuspendType::Process);
+ }
+
+ // Set ourselves as suspended.
+ SetSuspended(true);
+ } else {
+ ASSERT(activity == ProcessActivity::Runnable);
+
+ // Verify that we're suspended.
+ if (!is_suspended) {
+ return ResultInvalidState;
+ }
+
+ // Resume all threads.
+ for (auto* thread : GetThreadList()) {
+ thread->Resume(SuspendType::Process);
+ }
+
+ // Set ourselves as resumed.
+ SetSuspended(false);
+ }
+
+ return ResultSuccess;
+}
+
+Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) {
program_id = metadata.GetTitleID();
ideal_core = metadata.GetMainThreadCore();
is_64bit_process = metadata.Is64BitProgram();
@@ -364,24 +356,24 @@ ResultCode KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata,
return ResultLimitReached;
}
// Initialize proces address space
- if (const ResultCode result{
- page_table->InitializeForProcess(metadata.GetAddressSpaceType(), false, 0x8000000,
- code_size, KMemoryManager::Pool::Application)};
+ if (const Result result{page_table->InitializeForProcess(metadata.GetAddressSpaceType(), false,
+ 0x8000000, code_size,
+ KMemoryManager::Pool::Application)};
result.IsError()) {
return result;
}
// Map process code region
- if (const ResultCode result{page_table->MapProcessCode(page_table->GetCodeRegionStart(),
- code_size / PageSize, KMemoryState::Code,
- KMemoryPermission::None)};
+ if (const Result result{page_table->MapProcessCode(page_table->GetCodeRegionStart(),
+ code_size / PageSize, KMemoryState::Code,
+ KMemoryPermission::None)};
result.IsError()) {
return result;
}
// Initialize process capabilities
const auto& caps{metadata.GetKernelCapabilities()};
- if (const ResultCode result{
+ if (const Result result{
capabilities.InitializeForUserProcess(caps.data(), caps.size(), *page_table)};
result.IsError()) {
return result;
@@ -401,11 +393,11 @@ ResultCode KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata,
break;
default:
- UNREACHABLE();
+ ASSERT(false);
}
// Create TLS region
- tls_region_address = CreateTLSRegion();
+ R_TRY(this->CreateThreadLocalRegion(std::addressof(tls_region_address)));
memory_reservation.Commit();
return handle_table.Initialize(capabilities.GetHandleTableSize());
@@ -428,11 +420,11 @@ void KProcess::PrepareForTermination() {
ChangeStatus(ProcessStatus::Exiting);
const auto stop_threads = [this](const std::vector<KThread*>& in_thread_list) {
- for (auto& thread : in_thread_list) {
+ for (auto* thread : in_thread_list) {
if (thread->GetOwnerProcess() != this)
continue;
- if (thread == kernel.CurrentScheduler()->GetCurrentThread())
+ if (thread == GetCurrentThreadPointer(kernel))
continue;
// TODO(Subv): When are the other running/ready threads terminated?
@@ -445,7 +437,7 @@ void KProcess::PrepareForTermination() {
stop_threads(kernel.System().GlobalSchedulerContext().GetThreadList());
- FreeTLSRegion(tls_region_address);
+ this->DeleteThreadLocalRegion(tls_region_address);
tls_region_address = 0;
if (resource_limit) {
@@ -457,9 +449,6 @@ void KProcess::PrepareForTermination() {
}
void KProcess::Finalize() {
- // Finalize the handle table and close any open handles.
- handle_table.Finalize();
-
// Free all shared memory infos.
{
auto it = shared_memory_list.begin();
@@ -484,67 +473,156 @@ void KProcess::Finalize() {
resource_limit = nullptr;
}
+ // Finalize the page table.
+ page_table.reset();
+
// Perform inherited finalization.
KAutoObjectWithSlabHeapAndContainer<KProcess, KWorkerTask>::Finalize();
}
-/**
- * Attempts to find a TLS page that contains a free slot for
- * use by a thread.
- *
- * @returns If a page with an available slot is found, then an iterator
- * pointing to the page is returned. Otherwise the end iterator
- * is returned instead.
- */
-static auto FindTLSPageWithAvailableSlots(std::vector<TLSPage>& tls_pages) {
- return std::find_if(tls_pages.begin(), tls_pages.end(),
- [](const auto& page) { return page.HasAvailableSlots(); });
+Result KProcess::CreateThreadLocalRegion(VAddr* out) {
+ KThreadLocalPage* tlp = nullptr;
+ VAddr tlr = 0;
+
+ // See if we can get a region from a partially used TLP.
+ {
+ KScopedSchedulerLock sl{kernel};
+
+ if (auto it = partially_used_tlp_tree.begin(); it != partially_used_tlp_tree.end()) {
+ tlr = it->Reserve();
+ ASSERT(tlr != 0);
+
+ if (it->IsAllUsed()) {
+ tlp = std::addressof(*it);
+ partially_used_tlp_tree.erase(it);
+ fully_used_tlp_tree.insert(*tlp);
+ }
+
+ *out = tlr;
+ return ResultSuccess;
+ }
+ }
+
+ // Allocate a new page.
+ tlp = KThreadLocalPage::Allocate(kernel);
+ R_UNLESS(tlp != nullptr, ResultOutOfMemory);
+ auto tlp_guard = SCOPE_GUARD({ KThreadLocalPage::Free(kernel, tlp); });
+
+ // Initialize the new page.
+ R_TRY(tlp->Initialize(kernel, this));
+
+ // Reserve a TLR.
+ tlr = tlp->Reserve();
+ ASSERT(tlr != 0);
+
+ // Insert into our tree.
+ {
+ KScopedSchedulerLock sl{kernel};
+ if (tlp->IsAllUsed()) {
+ fully_used_tlp_tree.insert(*tlp);
+ } else {
+ partially_used_tlp_tree.insert(*tlp);
+ }
+ }
+
+ // We succeeded!
+ tlp_guard.Cancel();
+ *out = tlr;
+ return ResultSuccess;
}
-VAddr KProcess::CreateTLSRegion() {
- KScopedSchedulerLock lock(kernel);
- if (auto tls_page_iter{FindTLSPageWithAvailableSlots(tls_pages)};
- tls_page_iter != tls_pages.cend()) {
- return *tls_page_iter->ReserveSlot();
+Result KProcess::DeleteThreadLocalRegion(VAddr addr) {
+ KThreadLocalPage* page_to_free = nullptr;
+
+ // Release the region.
+ {
+ KScopedSchedulerLock sl{kernel};
+
+ // Try to find the page in the partially used list.
+ auto it = partially_used_tlp_tree.find_key(Common::AlignDown(addr, PageSize));
+ if (it == partially_used_tlp_tree.end()) {
+ // If we don't find it, it has to be in the fully used list.
+ it = fully_used_tlp_tree.find_key(Common::AlignDown(addr, PageSize));
+ R_UNLESS(it != fully_used_tlp_tree.end(), ResultInvalidAddress);
+
+ // Release the region.
+ it->Release(addr);
+
+ // Move the page out of the fully used list.
+ KThreadLocalPage* tlp = std::addressof(*it);
+ fully_used_tlp_tree.erase(it);
+ if (tlp->IsAllFree()) {
+ page_to_free = tlp;
+ } else {
+ partially_used_tlp_tree.insert(*tlp);
+ }
+ } else {
+ // Release the region.
+ it->Release(addr);
+
+ // Handle the all-free case.
+ KThreadLocalPage* tlp = std::addressof(*it);
+ if (tlp->IsAllFree()) {
+ partially_used_tlp_tree.erase(it);
+ page_to_free = tlp;
+ }
+ }
+ }
+
+ // If we should free the page it was in, do so.
+ if (page_to_free != nullptr) {
+ page_to_free->Finalize();
+
+ KThreadLocalPage::Free(kernel, page_to_free);
}
- Page* const tls_page_ptr{kernel.GetUserSlabHeapPages().Allocate()};
- ASSERT(tls_page_ptr);
+ return ResultSuccess;
+}
- const VAddr start{page_table->GetKernelMapRegionStart()};
- const VAddr size{page_table->GetKernelMapRegionEnd() - start};
- const PAddr tls_map_addr{kernel.System().DeviceMemory().GetPhysicalAddr(tls_page_ptr)};
- const VAddr tls_page_addr{page_table
- ->AllocateAndMapMemory(1, PageSize, true, start, size / PageSize,
- KMemoryState::ThreadLocal,
- KMemoryPermission::UserReadWrite,
- tls_map_addr)
- .ValueOr(0)};
+bool KProcess::InsertWatchpoint(Core::System& system, VAddr addr, u64 size,
+ DebugWatchpointType type) {
+ const auto watch{std::find_if(watchpoints.begin(), watchpoints.end(), [&](const auto& wp) {
+ return wp.type == DebugWatchpointType::None;
+ })};
- ASSERT(tls_page_addr);
+ if (watch == watchpoints.end()) {
+ return false;
+ }
- std::memset(tls_page_ptr, 0, PageSize);
- tls_pages.emplace_back(tls_page_addr);
+ watch->start_address = addr;
+ watch->end_address = addr + size;
+ watch->type = type;
- const auto reserve_result{tls_pages.back().ReserveSlot()};
- ASSERT(reserve_result.has_value());
+ for (VAddr page = Common::AlignDown(addr, PageSize); page < addr + size; page += PageSize) {
+ debug_page_refcounts[page]++;
+ system.Memory().MarkRegionDebug(page, PageSize, true);
+ }
- return *reserve_result;
+ return true;
}
-void KProcess::FreeTLSRegion(VAddr tls_address) {
- KScopedSchedulerLock lock(kernel);
- const VAddr aligned_address = Common::AlignDown(tls_address, Core::Memory::PAGE_SIZE);
- auto iter =
- std::find_if(tls_pages.begin(), tls_pages.end(), [aligned_address](const auto& page) {
- return page.GetBaseAddress() == aligned_address;
- });
+bool KProcess::RemoveWatchpoint(Core::System& system, VAddr addr, u64 size,
+ DebugWatchpointType type) {
+ const auto watch{std::find_if(watchpoints.begin(), watchpoints.end(), [&](const auto& wp) {
+ return wp.start_address == addr && wp.end_address == addr + size && wp.type == type;
+ })};
- // Something has gone very wrong if we're freeing a region
- // with no actual page available.
- ASSERT(iter != tls_pages.cend());
+ if (watch == watchpoints.end()) {
+ return false;
+ }
+
+ watch->start_address = 0;
+ watch->end_address = 0;
+ watch->type = DebugWatchpointType::None;
+
+ for (VAddr page = Common::AlignDown(addr, PageSize); page < addr + size; page += PageSize) {
+ debug_page_refcounts[page]--;
+ if (!debug_page_refcounts[page]) {
+ system.Memory().MarkRegionDebug(page, PageSize, false);
+ }
+ }
- iter->ReleaseSlot(tls_address);
+ return true;
}
void KProcess::LoadModule(CodeSet code_set, VAddr base_addr) {
@@ -567,9 +645,10 @@ bool KProcess::IsSignaled() const {
}
KProcess::KProcess(KernelCore& kernel_)
- : KAutoObjectWithSlabHeapAndContainer{kernel_},
- page_table{std::make_unique<KPageTable>(kernel_.System())}, handle_table{kernel_},
- address_arbiter{kernel_.System()}, condition_var{kernel_.System()}, state_lock{kernel_} {}
+ : KAutoObjectWithSlabHeapAndContainer{kernel_}, page_table{std::make_unique<KPageTable>(
+ kernel_.System())},
+ handle_table{kernel_}, address_arbiter{kernel_.System()}, condition_var{kernel_.System()},
+ state_lock{kernel_}, list_lock{kernel_} {}
KProcess::~KProcess() = default;
@@ -583,7 +662,7 @@ void KProcess::ChangeStatus(ProcessStatus new_status) {
NotifyAvailable();
}
-ResultCode KProcess::AllocateMainThreadStack(std::size_t stack_size) {
+Result KProcess::AllocateMainThreadStack(std::size_t stack_size) {
ASSERT(stack_size);
// The kernel always ensures that the given stack size is page aligned.
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index 38b446350..d56d73bab 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -1,20 +1,20 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
#include <list>
+#include <map>
#include <string>
-#include <vector>
#include "common/common_types.h"
#include "core/hle/kernel/k_address_arbiter.h"
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_condition_variable.h"
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/k_thread_local_page.h"
#include "core/hle/kernel/k_worker_task.h"
#include "core/hle/kernel/process_capability.h"
#include "core/hle/kernel/slab_helpers.h"
@@ -63,6 +63,25 @@ enum class ProcessStatus {
DebugBreak,
};
+enum class ProcessActivity : u32 {
+ Runnable,
+ Paused,
+};
+
+enum class DebugWatchpointType : u8 {
+ None = 0,
+ Read = 1 << 0,
+ Write = 1 << 1,
+ ReadOrWrite = Read | Write,
+};
+DECLARE_ENUM_FLAG_OPERATORS(DebugWatchpointType);
+
+struct DebugWatchpoint {
+ VAddr start_address;
+ VAddr end_address;
+ DebugWatchpointType type;
+};
+
class KProcess final : public KAutoObjectWithSlabHeapAndContainer<KProcess, KWorkerTask> {
KERNEL_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject);
@@ -90,8 +109,8 @@ public:
static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4;
- static ResultCode Initialize(KProcess* process, Core::System& system, std::string process_name,
- ProcessType type);
+ static Result Initialize(KProcess* process, Core::System& system, std::string process_name,
+ ProcessType type, KResourceLimit* res_limit);
/// Gets a reference to the process' page table.
KPageTable& PageTable() {
@@ -113,11 +132,11 @@ public:
return handle_table;
}
- ResultCode SignalToAddress(VAddr address) {
+ Result SignalToAddress(VAddr address) {
return condition_var.SignalToAddress(address);
}
- ResultCode WaitForAddress(Handle handle, VAddr address, u32 tag) {
+ Result WaitForAddress(Handle handle, VAddr address, u32 tag) {
return condition_var.WaitForAddress(handle, address, tag);
}
@@ -125,17 +144,16 @@ public:
return condition_var.Signal(cv_key, count);
}
- ResultCode WaitConditionVariable(VAddr address, u64 cv_key, u32 tag, s64 ns) {
+ Result WaitConditionVariable(VAddr address, u64 cv_key, u32 tag, s64 ns) {
return condition_var.Wait(address, cv_key, tag, ns);
}
- ResultCode SignalAddressArbiter(VAddr address, Svc::SignalType signal_type, s32 value,
- s32 count) {
+ Result SignalAddressArbiter(VAddr address, Svc::SignalType signal_type, s32 value, s32 count) {
return address_arbiter.SignalToAddress(address, signal_type, value, count);
}
- ResultCode WaitAddressArbiter(VAddr address, Svc::ArbitrationType arb_type, s32 value,
- s64 timeout) {
+ Result WaitAddressArbiter(VAddr address, Svc::ArbitrationType arb_type, s32 value,
+ s64 timeout) {
return address_arbiter.WaitForAddress(address, arb_type, value, timeout);
}
@@ -282,17 +300,17 @@ public:
u64 GetTotalPhysicalMemoryUsedWithoutSystemResource() const;
/// Gets the list of all threads created with this process as their owner.
- const std::list<const KThread*>& GetThreadList() const {
+ std::list<KThread*>& GetThreadList() {
return thread_list;
}
/// Registers a thread as being created under this process,
/// adding it to this process' thread list.
- void RegisterThread(const KThread* thread);
+ void RegisterThread(KThread* thread);
/// Unregisters a thread from this process, removing it
/// from this process' thread list.
- void UnregisterThread(const KThread* thread);
+ void UnregisterThread(KThread* thread);
/// Clears the signaled state of the process if and only if it's signaled.
///
@@ -302,7 +320,7 @@ public:
/// @pre The process must be in a signaled state. If this is called on a
/// process instance that is not signaled, ERR_INVALID_STATE will be
/// returned.
- ResultCode Reset();
+ Result Reset();
/**
* Loads process-specifics configuration info with metadata provided
@@ -313,7 +331,7 @@ public:
* @returns ResultSuccess if all relevant metadata was able to be
* loaded and parsed. Otherwise, an error code is returned.
*/
- ResultCode LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size);
+ Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size);
/**
* Starts the main application thread for this process.
@@ -347,6 +365,8 @@ public:
void DoWorkerTaskImpl();
+ Result SetActivity(ProcessActivity activity);
+
void PinCurrentThread(s32 core_id);
void UnpinCurrentThread(s32 core_id);
void UnpinThread(KThread* thread);
@@ -355,17 +375,30 @@ public:
return state_lock;
}
- ResultCode AddSharedMemory(KSharedMemory* shmem, VAddr address, size_t size);
+ Result AddSharedMemory(KSharedMemory* shmem, VAddr address, size_t size);
void RemoveSharedMemory(KSharedMemory* shmem, VAddr address, size_t size);
///////////////////////////////////////////////////////////////////////////////////////////////
// Thread-local storage management
// Marks the next available region as used and returns the address of the slot.
- [[nodiscard]] VAddr CreateTLSRegion();
+ [[nodiscard]] Result CreateThreadLocalRegion(VAddr* out);
// Frees a used TLS slot identified by the given address
- void FreeTLSRegion(VAddr tls_address);
+ Result DeleteThreadLocalRegion(VAddr addr);
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Debug watchpoint management
+
+ // Attempts to insert a watchpoint into a free slot. Returns false if none are available.
+ bool InsertWatchpoint(Core::System& system, VAddr addr, u64 size, DebugWatchpointType type);
+
+ // Attempts to remove the watchpoint specified by the given parameters.
+ bool RemoveWatchpoint(Core::System& system, VAddr addr, u64 size, DebugWatchpointType type);
+
+ const std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>& GetWatchpoints() const {
+ return watchpoints;
+ }
private:
void PinThread(s32 core_id, KThread* thread) {
@@ -388,7 +421,7 @@ private:
void ChangeStatus(ProcessStatus new_status);
/// Allocates the main thread stack for the process, given the stack size in bytes.
- ResultCode AllocateMainThreadStack(std::size_t stack_size);
+ Result AllocateMainThreadStack(std::size_t stack_size);
/// Memory manager for this process
std::unique_ptr<KPageTable> page_table;
@@ -413,13 +446,6 @@ private:
/// The ideal CPU core for this process, threads are scheduled on this core by default.
u8 ideal_core = 0;
- /// The Thread Local Storage area is allocated as processes create threads,
- /// each TLS area is 0x200 bytes, so one page (0x1000) is split up in 8 parts, and each part
- /// holds the TLS for a specific thread. This vector contains which parts are in use for each
- /// page as a bitmask.
- /// This vector will grow as more pages are allocated for new threads.
- std::vector<TLSPage> tls_pages;
-
/// Contains the parsed process capability descriptors.
ProcessCapabilities capabilities;
@@ -429,7 +455,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;
@@ -449,7 +475,7 @@ private:
std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy{};
/// List of threads that are running with this process as their owner.
- std::list<const KThread*> thread_list;
+ std::list<KThread*> thread_list;
/// List of shared memory that are running with this process as their owner.
std::list<KSharedMemoryInfo*> shared_memory_list;
@@ -478,10 +504,19 @@ private:
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> running_threads{};
std::array<u64, Core::Hardware::NUM_CPU_CORES> running_thread_idle_counts{};
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> pinned_threads{};
+ std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> watchpoints{};
+ std::map<VAddr, u64> debug_page_refcounts;
KThread* exception_thread{};
KLightLock state_lock;
+ KLightLock list_lock;
+
+ using TLPTree =
+ Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>;
+ using TLPIterator = TLPTree::iterator;
+ TLPTree fully_used_tlp_tree;
+ TLPTree partially_used_tlp_tree;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_readable_event.cpp b/src/core/hle/kernel/k_readable_event.cpp
index bf1db10d4..94c5464fe 100644
--- a/src/core/hle/kernel/k_readable_event.cpp
+++ b/src/core/hle/kernel/k_readable_event.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "core/hle/kernel/k_event.h"
@@ -28,7 +27,7 @@ void KReadableEvent::Destroy() {
}
}
-ResultCode KReadableEvent::Signal() {
+Result KReadableEvent::Signal() {
KScopedSchedulerLock lk{kernel};
if (!is_signaled) {
@@ -39,13 +38,13 @@ ResultCode KReadableEvent::Signal() {
return ResultSuccess;
}
-ResultCode KReadableEvent::Clear() {
+Result KReadableEvent::Clear() {
Reset();
return ResultSuccess;
}
-ResultCode KReadableEvent::Reset() {
+Result KReadableEvent::Reset() {
KScopedSchedulerLock lk{kernel};
if (!is_signaled) {
diff --git a/src/core/hle/kernel/k_readable_event.h b/src/core/hle/kernel/k_readable_event.h
index 149fa78dd..18dcad289 100644
--- a/src/core/hle/kernel/k_readable_event.h
+++ b/src/core/hle/kernel/k_readable_event.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -34,9 +33,9 @@ public:
bool IsSignaled() const override;
void Destroy() override;
- ResultCode Signal();
- ResultCode Clear();
- ResultCode Reset();
+ Result Signal();
+ Result Clear();
+ Result Reset();
private:
bool is_signaled{};
diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp
index 0c4bba66b..010dcf99e 100644
--- a/src/core/hle/kernel/k_resource_limit.cpp
+++ b/src/core/hle/kernel/k_resource_limit.cpp
@@ -1,8 +1,8 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
+#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/svc_results.h"
@@ -73,7 +73,7 @@ s64 KResourceLimit::GetFreeValue(LimitableResource which) const {
return value;
}
-ResultCode KResourceLimit::SetLimitValue(LimitableResource which, s64 value) {
+Result KResourceLimit::SetLimitValue(LimitableResource which, s64 value) {
const auto index = static_cast<std::size_t>(which);
KScopedLightLock lk(lock);
R_UNLESS(current_values[index] <= value, ResultInvalidState);
@@ -151,4 +151,22 @@ void KResourceLimit::Release(LimitableResource which, s64 value, s64 hint) {
}
}
+KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) {
+ auto* resource_limit = KResourceLimit::Create(system.Kernel());
+ resource_limit->Initialize(&system.CoreTiming());
+
+ // Initialize default resource limit values.
+ // TODO(bunnei): These values are the system defaults, the limits for service processes are
+ // lower. These should use the correct limit values.
+
+ ASSERT(resource_limit->SetLimitValue(LimitableResource::PhysicalMemory, physical_memory_size)
+ .IsSuccess());
+ ASSERT(resource_limit->SetLimitValue(LimitableResource::Threads, 800).IsSuccess());
+ ASSERT(resource_limit->SetLimitValue(LimitableResource::Events, 900).IsSuccess());
+ ASSERT(resource_limit->SetLimitValue(LimitableResource::TransferMemory, 200).IsSuccess());
+ ASSERT(resource_limit->SetLimitValue(LimitableResource::Sessions, 1133).IsSuccess());
+
+ return resource_limit;
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_resource_limit.h b/src/core/hle/kernel/k_resource_limit.h
index fab6005ff..65c98c979 100644
--- a/src/core/hle/kernel/k_resource_limit.h
+++ b/src/core/hle/kernel/k_resource_limit.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -9,7 +8,7 @@
#include "core/hle/kernel/k_light_condition_variable.h"
#include "core/hle/kernel/k_light_lock.h"
-union ResultCode;
+union Result;
namespace Core::Timing {
class CoreTiming;
@@ -47,7 +46,7 @@ public:
s64 GetPeakValue(LimitableResource which) const;
s64 GetFreeValue(LimitableResource which) const;
- ResultCode SetLimitValue(LimitableResource which, s64 value);
+ Result SetLimitValue(LimitableResource which, s64 value);
bool Reserve(LimitableResource which, s64 value);
bool Reserve(LimitableResource which, s64 value, s64 timeout);
@@ -67,4 +66,7 @@ private:
KLightConditionVariable cond_var;
const Core::Timing::CoreTiming* core_timing{};
};
+
+KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size);
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp
index c96520828..c34ce7a17 100644
--- a/src/core/hle/kernel/k_scheduler.cpp
+++ b/src/core/hle/kernel/k_scheduler.cpp
@@ -1,9 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-// This file references various implementation details from Atmosphere, an open-source firmware for
-// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <bit>
@@ -22,7 +18,6 @@
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
-#include "core/hle/kernel/time_manager.h"
namespace Kernel {
@@ -32,69 +27,185 @@ static void IncrementScheduledCount(Kernel::KThread* thread) {
}
}
-void KScheduler::RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule) {
- auto scheduler = kernel.CurrentScheduler();
+KScheduler::KScheduler(KernelCore& kernel_) : kernel{kernel_} {
+ m_switch_fiber = std::make_shared<Common::Fiber>([this] {
+ while (true) {
+ ScheduleImplFiber();
+ }
+ });
+
+ m_state.needs_scheduling = true;
+}
+
+KScheduler::~KScheduler() = default;
- u32 current_core{0xF};
- bool must_context_switch{};
- if (scheduler) {
- current_core = scheduler->core_id;
- // TODO(bunnei): Should be set to true when we deprecate single core
- must_context_switch = !kernel.IsPhantomModeForSingleCore();
+void KScheduler::SetInterruptTaskRunnable() {
+ m_state.interrupt_task_runnable = true;
+ m_state.needs_scheduling = true;
+}
+
+void KScheduler::RequestScheduleOnInterrupt() {
+ m_state.needs_scheduling = true;
+
+ if (CanSchedule(kernel)) {
+ ScheduleOnInterrupt();
}
+}
- while (cores_pending_reschedule != 0) {
- const auto core = static_cast<u32>(std::countr_zero(cores_pending_reschedule));
- ASSERT(core < Core::Hardware::NUM_CPU_CORES);
- if (!must_context_switch || core != current_core) {
- auto& phys_core = kernel.PhysicalCore(core);
- phys_core.Interrupt();
- }
- cores_pending_reschedule &= ~(1ULL << core);
+void KScheduler::DisableScheduling(KernelCore& kernel) {
+ ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0);
+ GetCurrentThread(kernel).DisableDispatch();
+}
+
+void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) {
+ ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 1);
+
+ auto* scheduler{kernel.CurrentScheduler()};
+
+ if (!scheduler || kernel.IsPhantomModeForSingleCore()) {
+ KScheduler::RescheduleCores(kernel, cores_needing_scheduling);
+ KScheduler::RescheduleCurrentHLEThread(kernel);
+ return;
}
- for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; ++core_id) {
- if (kernel.PhysicalCore(core_id).IsInterrupted()) {
- KInterruptManager::HandleInterrupt(kernel, static_cast<s32>(core_id));
- }
+ scheduler->RescheduleOtherCores(cores_needing_scheduling);
+
+ if (GetCurrentThread(kernel).GetDisableDispatchCount() > 1) {
+ GetCurrentThread(kernel).EnableDispatch();
+ } else {
+ scheduler->RescheduleCurrentCore();
}
+}
+
+void KScheduler::RescheduleCurrentHLEThread(KernelCore& kernel) {
+ // HACK: we cannot schedule from this thread, it is not a core thread
+ ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
+
+ // Special case to ensure dummy threads that are waiting block
+ GetCurrentThread(kernel).IfDummyThreadTryWait();
- if (must_context_switch) {
- auto core_scheduler = kernel.CurrentScheduler();
- kernel.ExitSVCProfile();
- core_scheduler->RescheduleCurrentCore();
- kernel.EnterSVCProfile();
+ ASSERT(GetCurrentThread(kernel).GetState() != ThreadState::Waiting);
+ GetCurrentThread(kernel).EnableDispatch();
+}
+
+u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
+ if (IsSchedulerUpdateNeeded(kernel)) {
+ return UpdateHighestPriorityThreadsImpl(kernel);
+ } else {
+ return 0;
}
}
+void KScheduler::Schedule() {
+ ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
+ ASSERT(m_core_id == GetCurrentCoreId(kernel));
+
+ ScheduleImpl();
+}
+
+void KScheduler::ScheduleOnInterrupt() {
+ GetCurrentThread(kernel).DisableDispatch();
+ Schedule();
+ GetCurrentThread(kernel).EnableDispatch();
+}
+
+void KScheduler::PreemptSingleCore() {
+ GetCurrentThread(kernel).DisableDispatch();
+
+ auto* thread = GetCurrentThreadPointer(kernel);
+ auto& previous_scheduler = kernel.Scheduler(thread->GetCurrentCore());
+ previous_scheduler.Unload(thread);
+
+ Common::Fiber::YieldTo(thread->GetHostContext(), *m_switch_fiber);
+
+ GetCurrentThread(kernel).EnableDispatch();
+}
+
+void KScheduler::RescheduleCurrentCore() {
+ ASSERT(!kernel.IsPhantomModeForSingleCore());
+ ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
+
+ GetCurrentThread(kernel).EnableDispatch();
+
+ if (m_state.needs_scheduling.load()) {
+ // Disable interrupts, and then check again if rescheduling is needed.
+ // KScopedInterruptDisable intr_disable;
+
+ kernel.CurrentScheduler()->RescheduleCurrentCoreImpl();
+ }
+}
+
+void KScheduler::RescheduleCurrentCoreImpl() {
+ // Check that scheduling is needed.
+ if (m_state.needs_scheduling.load()) [[likely]] {
+ GetCurrentThread(kernel).DisableDispatch();
+ Schedule();
+ GetCurrentThread(kernel).EnableDispatch();
+ }
+}
+
+void KScheduler::Initialize(KThread* main_thread, KThread* idle_thread, s32 core_id) {
+ // Set core ID/idle thread/interrupt task manager.
+ m_core_id = core_id;
+ m_idle_thread = idle_thread;
+ // m_state.idle_thread_stack = m_idle_thread->GetStackTop();
+ // m_state.interrupt_task_manager = &kernel.GetInterruptTaskManager();
+
+ // Insert the main thread into the priority queue.
+ // {
+ // KScopedSchedulerLock lk{kernel};
+ // GetPriorityQueue(kernel).PushBack(GetCurrentThreadPointer(kernel));
+ // SetSchedulerUpdateNeeded(kernel);
+ // }
+
+ // Bind interrupt handler.
+ // kernel.GetInterruptManager().BindHandler(
+ // GetSchedulerInterruptHandler(kernel), KInterruptName::Scheduler, m_core_id,
+ // KInterruptController::PriorityLevel::Scheduler, false, false);
+
+ // Set the current thread.
+ m_current_thread = main_thread;
+}
+
+void KScheduler::Activate() {
+ ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
+
+ // m_state.should_count_idle = KTargetSystem::IsDebugMode();
+ m_is_active = true;
+ RescheduleCurrentCore();
+}
+
+void KScheduler::OnThreadStart() {
+ GetCurrentThread(kernel).EnableDispatch();
+}
+
u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) {
- KScopedSpinLock lk{guard};
- if (KThread* prev_highest_thread = state.highest_priority_thread;
- prev_highest_thread != highest_thread) {
- if (prev_highest_thread != nullptr) {
+ if (KThread* prev_highest_thread = m_state.highest_priority_thread;
+ prev_highest_thread != highest_thread) [[likely]] {
+ if (prev_highest_thread != nullptr) [[likely]] {
IncrementScheduledCount(prev_highest_thread);
- prev_highest_thread->SetLastScheduledTick(system.CoreTiming().GetCPUTicks());
+ prev_highest_thread->SetLastScheduledTick(kernel.System().CoreTiming().GetCPUTicks());
}
- if (state.should_count_idle) {
- if (highest_thread != nullptr) {
+ if (m_state.should_count_idle) {
+ if (highest_thread != nullptr) [[likely]] {
if (KProcess* process = highest_thread->GetOwnerProcess(); process != nullptr) {
- process->SetRunningThread(core_id, highest_thread, state.idle_count);
+ process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count);
}
} else {
- state.idle_count++;
+ m_state.idle_count++;
}
}
- state.highest_priority_thread = highest_thread;
- state.needs_scheduling.store(true);
- return (1ULL << core_id);
+ m_state.highest_priority_thread = highest_thread;
+ m_state.needs_scheduling = true;
+ return (1ULL << m_core_id);
} else {
return 0;
}
}
u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
- ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+ ASSERT(IsSchedulerLockedByCurrentThread(kernel));
// Clear that we need to update.
ClearSchedulerUpdateNeeded(kernel);
@@ -103,18 +214,20 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
KThread* top_threads[Core::Hardware::NUM_CPU_CORES];
auto& priority_queue = GetPriorityQueue(kernel);
- /// We want to go over all cores, finding the highest priority thread and determining if
- /// scheduling is needed for that core.
+ // We want to go over all cores, finding the highest priority thread and determining if
+ // scheduling is needed for that core.
for (size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
KThread* top_thread = priority_queue.GetScheduledFront(static_cast<s32>(core_id));
if (top_thread != nullptr) {
- // If the thread has no waiters, we need to check if the process has a thread pinned.
- if (top_thread->GetNumKernelWaiters() == 0) {
- if (KProcess* parent = top_thread->GetOwnerProcess(); parent != nullptr) {
- if (KThread* pinned = parent->GetPinnedThread(static_cast<s32>(core_id));
- pinned != nullptr && pinned != top_thread) {
- // We prefer our parent's pinned thread if possible. However, we also don't
- // want to schedule un-runnable threads.
+ // We need to check if the thread's process has a pinned thread.
+ if (KProcess* parent = top_thread->GetOwnerProcess()) {
+ // Check that there's a pinned thread other than the current top thread.
+ if (KThread* pinned = parent->GetPinnedThread(static_cast<s32>(core_id));
+ pinned != nullptr && pinned != top_thread) {
+ // We need to prefer threads with kernel waiters to the pinned thread.
+ if (top_thread->GetNumKernelWaiters() ==
+ 0 /* && top_thread != parent->GetExceptionThread() */) {
+ // If the pinned thread is runnable, use it.
if (pinned->GetRawState() == ThreadState::Runnable) {
top_thread = pinned;
} else {
@@ -134,7 +247,8 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
// Idle cores are bad. We're going to try to migrate threads to each idle core in turn.
while (idle_cores != 0) {
- const auto core_id = static_cast<u32>(std::countr_zero(idle_cores));
+ const s32 core_id = static_cast<s32>(std::countr_zero(idle_cores));
+
if (KThread* suggested = priority_queue.GetSuggestedFront(core_id); suggested != nullptr) {
s32 migration_candidates[Core::Hardware::NUM_CPU_CORES];
size_t num_candidates = 0;
@@ -155,7 +269,6 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
// The suggested thread isn't bound to its core, so we can migrate it!
suggested->SetActiveCore(core_id);
priority_queue.ChangeCore(suggested_core, suggested);
-
top_threads[core_id] = suggested;
cores_needing_scheduling |=
kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]);
@@ -188,7 +301,6 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
// Perform the migration.
suggested->SetActiveCore(core_id);
priority_queue.ChangeCore(candidate_core, suggested);
-
top_threads[core_id] = suggested;
cores_needing_scheduling |=
kernel.Scheduler(core_id).UpdateHighestPriorityThread(
@@ -205,24 +317,210 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
return cores_needing_scheduling;
}
+void KScheduler::SwitchThread(KThread* next_thread) {
+ KProcess* const cur_process = kernel.CurrentProcess();
+ KThread* const cur_thread = GetCurrentThreadPointer(kernel);
+
+ // We never want to schedule a null thread, so use the idle thread if we don't have a next.
+ if (next_thread == nullptr) {
+ next_thread = m_idle_thread;
+ }
+
+ if (next_thread->GetCurrentCore() != m_core_id) {
+ next_thread->SetCurrentCore(m_core_id);
+ }
+
+ // If we're not actually switching thread, there's nothing to do.
+ if (next_thread == cur_thread) {
+ return;
+ }
+
+ // Next thread is now known not to be nullptr, and must not be dispatchable.
+ ASSERT(next_thread->GetDisableDispatchCount() == 1);
+ ASSERT(!next_thread->IsDummyThread());
+
+ // Update the CPU time tracking variables.
+ const s64 prev_tick = m_last_context_switch_time;
+ const s64 cur_tick = kernel.System().CoreTiming().GetCPUTicks();
+ const s64 tick_diff = cur_tick - prev_tick;
+ cur_thread->AddCpuTime(m_core_id, tick_diff);
+ if (cur_process != nullptr) {
+ cur_process->UpdateCPUTimeTicks(tick_diff);
+ }
+ m_last_context_switch_time = cur_tick;
+
+ // Update our previous thread.
+ if (cur_process != nullptr) {
+ if (!cur_thread->IsTerminationRequested() && cur_thread->GetActiveCore() == m_core_id)
+ [[likely]] {
+ m_state.prev_thread = cur_thread;
+ } else {
+ m_state.prev_thread = nullptr;
+ }
+ }
+
+ // Switch the current process, if we're switching processes.
+ // if (KProcess *next_process = next_thread->GetOwnerProcess(); next_process != cur_process) {
+ // KProcess::Switch(cur_process, next_process);
+ // }
+
+ // Set the new thread.
+ SetCurrentThread(kernel, next_thread);
+ m_current_thread = next_thread;
+
+ // Set the new Thread Local region.
+ // cpu::SwitchThreadLocalRegion(GetInteger(next_thread->GetThreadLocalRegionAddress()));
+}
+
+void KScheduler::ScheduleImpl() {
+ // First, clear the needs scheduling bool.
+ m_state.needs_scheduling.store(false, std::memory_order_seq_cst);
+
+ // Load the appropriate thread pointers for scheduling.
+ KThread* const cur_thread{GetCurrentThreadPointer(kernel)};
+ KThread* highest_priority_thread{m_state.highest_priority_thread};
+
+ // Check whether there are runnable interrupt tasks.
+ if (m_state.interrupt_task_runnable) {
+ // The interrupt task is runnable.
+ // We want to switch to the interrupt task/idle thread.
+ highest_priority_thread = nullptr;
+ }
+
+ // If there aren't, we want to check if the highest priority thread is the same as the current
+ // thread.
+ if (highest_priority_thread == cur_thread) {
+ // If they're the same, then we can just return.
+ return;
+ }
+
+ // The highest priority thread is not the same as the current thread.
+ // Jump to the switcher and continue executing from there.
+ m_switch_cur_thread = cur_thread;
+ m_switch_highest_priority_thread = highest_priority_thread;
+ m_switch_from_schedule = true;
+ Common::Fiber::YieldTo(cur_thread->host_context, *m_switch_fiber);
+
+ // Returning from ScheduleImpl occurs after this thread has been scheduled again.
+}
+
+void KScheduler::ScheduleImplFiber() {
+ KThread* const cur_thread{m_switch_cur_thread};
+ KThread* highest_priority_thread{m_switch_highest_priority_thread};
+
+ // If we're not coming from scheduling (i.e., we came from SC preemption),
+ // we should restart the scheduling loop directly. Not accurate to HOS.
+ if (!m_switch_from_schedule) {
+ goto retry;
+ }
+
+ // Mark that we are not coming from scheduling anymore.
+ m_switch_from_schedule = false;
+
+ // Save the original thread context.
+ Unload(cur_thread);
+
+ // The current thread's context has been entirely taken care of.
+ // Now we want to loop until we successfully switch the thread context.
+ while (true) {
+ // We're starting to try to do the context switch.
+ // Check if the highest priority thread is null.
+ if (!highest_priority_thread) {
+ // The next thread is nullptr!
+
+ // Switch to the idle thread. Note: HOS treats idling as a special case for
+ // performance. This is not *required* for yuzu's purposes, and for singlecore
+ // compatibility, we can just move the logic that would go here into the execution
+ // of the idle thread. If we ever remove singlecore, we should implement this
+ // accurately to HOS.
+ highest_priority_thread = m_idle_thread;
+ }
+
+ // We want to try to lock the highest priority thread's context.
+ // Try to take it.
+ while (!highest_priority_thread->context_guard.try_lock()) {
+ // The highest priority thread's context is already locked.
+ // Check if we need scheduling. If we don't, we can retry directly.
+ if (m_state.needs_scheduling.load(std::memory_order_seq_cst)) {
+ // If we do, another core is interfering, and we must start again.
+ goto retry;
+ }
+ }
+
+ // It's time to switch the thread.
+ // Switch to the highest priority thread.
+ SwitchThread(highest_priority_thread);
+
+ // Check if we need scheduling. If we do, then we can't complete the switch and should
+ // retry.
+ if (m_state.needs_scheduling.load(std::memory_order_seq_cst)) {
+ // Our switch failed.
+ // We should unlock the thread context, and then retry.
+ highest_priority_thread->context_guard.unlock();
+ goto retry;
+ } else {
+ break;
+ }
+
+ retry:
+
+ // We failed to successfully do the context switch, and need to retry.
+ // Clear needs_scheduling.
+ m_state.needs_scheduling.store(false, std::memory_order_seq_cst);
+
+ // Refresh the highest priority thread.
+ highest_priority_thread = m_state.highest_priority_thread;
+ }
+
+ // Reload the guest thread context.
+ Reload(highest_priority_thread);
+
+ // Reload the host thread.
+ Common::Fiber::YieldTo(m_switch_fiber, *highest_priority_thread->host_context);
+}
+
+void KScheduler::Unload(KThread* thread) {
+ auto& cpu_core = kernel.System().ArmInterface(m_core_id);
+ cpu_core.SaveContext(thread->GetContext32());
+ cpu_core.SaveContext(thread->GetContext64());
+ // Save the TPIDR_EL0 system register in case it was modified.
+ thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
+ cpu_core.ClearExclusiveState();
+
+ // Check if the thread is terminated by checking the DPC flags.
+ if ((thread->GetStackParameters().dpc_flags & static_cast<u32>(DpcFlag::Terminated)) == 0) {
+ // The thread isn't terminated, so we want to unlock it.
+ thread->context_guard.unlock();
+ }
+}
+
+void KScheduler::Reload(KThread* thread) {
+ auto& cpu_core = kernel.System().ArmInterface(m_core_id);
+ cpu_core.LoadContext(thread->GetContext32());
+ cpu_core.LoadContext(thread->GetContext64());
+ cpu_core.SetTlsAddress(thread->GetTLSAddress());
+ cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
+ cpu_core.LoadWatchpointArray(thread->GetOwnerProcess()->GetWatchpoints());
+ cpu_core.ClearExclusiveState();
+}
+
void KScheduler::ClearPreviousThread(KernelCore& kernel, KThread* thread) {
- ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+ ASSERT(IsSchedulerLockedByCurrentThread(kernel));
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; ++i) {
// Get an atomic reference to the core scheduler's previous thread.
- std::atomic_ref<KThread*> prev_thread(kernel.Scheduler(static_cast<s32>(i)).prev_thread);
- static_assert(std::atomic_ref<KThread*>::is_always_lock_free);
+ auto& prev_thread{kernel.Scheduler(i).m_state.prev_thread};
// Atomically clear the previous thread if it's our target.
KThread* compare = thread;
- prev_thread.compare_exchange_strong(compare, nullptr);
+ prev_thread.compare_exchange_strong(compare, nullptr, std::memory_order_seq_cst);
}
}
void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state) {
- ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+ ASSERT(IsSchedulerLockedByCurrentThread(kernel));
// Check if the state has changed, because if it hasn't there's nothing to do.
- const auto cur_state = thread->GetRawState();
+ const ThreadState cur_state = thread->GetRawState();
if (cur_state == old_state) {
return;
}
@@ -242,12 +540,12 @@ void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, Threa
}
void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority) {
- ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+ ASSERT(IsSchedulerLockedByCurrentThread(kernel));
// If the thread is runnable, we want to change its priority in the queue.
if (thread->GetRawState() == ThreadState::Runnable) {
GetPriorityQueue(kernel).ChangePriority(old_priority,
- thread == kernel.GetCurrentEmuThread(), thread);
+ thread == GetCurrentThreadPointer(kernel), thread);
IncrementScheduledCount(thread);
SetSchedulerUpdateNeeded(kernel);
}
@@ -255,7 +553,7 @@ void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s3
void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread,
const KAffinityMask& old_affinity, s32 old_core) {
- ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+ ASSERT(IsSchedulerLockedByCurrentThread(kernel));
// If the thread is runnable, we want to change its affinity in the queue.
if (thread->GetRawState() == ThreadState::Runnable) {
@@ -265,15 +563,14 @@ void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread
}
}
-void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
- ASSERT(system.GlobalSchedulerContext().IsLocked());
+void KScheduler::RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 priority) {
+ ASSERT(IsSchedulerLockedByCurrentThread(kernel));
// Get a reference to the priority queue.
- auto& kernel = system.Kernel();
auto& priority_queue = GetPriorityQueue(kernel);
// Rotate the front of the queue to the end.
- KThread* top_thread = priority_queue.GetScheduledFront(cpu_core_id, priority);
+ KThread* top_thread = priority_queue.GetScheduledFront(core_id, priority);
KThread* next_thread = nullptr;
if (top_thread != nullptr) {
next_thread = priority_queue.MoveToScheduledBack(top_thread);
@@ -285,7 +582,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
// While we have a suggested thread, try to migrate it!
{
- KThread* suggested = priority_queue.GetSuggestedFront(cpu_core_id, priority);
+ KThread* suggested = priority_queue.GetSuggestedFront(core_id, priority);
while (suggested != nullptr) {
// Check if the suggested thread is the top thread on its core.
const s32 suggested_core = suggested->GetActiveCore();
@@ -306,7 +603,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
// to the front of the queue.
if (top_on_suggested_core == nullptr ||
top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) {
- suggested->SetActiveCore(cpu_core_id);
+ suggested->SetActiveCore(core_id);
priority_queue.ChangeCore(suggested_core, suggested, true);
IncrementScheduledCount(suggested);
break;
@@ -314,22 +611,21 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
}
// Get the next suggestion.
- suggested = priority_queue.GetSamePriorityNext(cpu_core_id, suggested);
+ suggested = priority_queue.GetSamePriorityNext(core_id, suggested);
}
}
// Now that we might have migrated a thread with the same priority, check if we can do better.
-
{
- KThread* best_thread = priority_queue.GetScheduledFront(cpu_core_id);
- if (best_thread == GetCurrentThread()) {
- best_thread = priority_queue.GetScheduledNext(cpu_core_id, best_thread);
+ KThread* best_thread = priority_queue.GetScheduledFront(core_id);
+ if (best_thread == GetCurrentThreadPointer(kernel)) {
+ best_thread = priority_queue.GetScheduledNext(core_id, best_thread);
}
// If the best thread we can choose has a priority the same or worse than ours, try to
// migrate a higher priority thread.
if (best_thread != nullptr && best_thread->GetPriority() >= priority) {
- KThread* suggested = priority_queue.GetSuggestedFront(cpu_core_id);
+ KThread* suggested = priority_queue.GetSuggestedFront(core_id);
while (suggested != nullptr) {
// If the suggestion's priority is the same as ours, don't bother.
if (suggested->GetPriority() >= best_thread->GetPriority()) {
@@ -348,7 +644,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
if (top_on_suggested_core == nullptr ||
top_on_suggested_core->GetPriority() >=
HighestCoreMigrationAllowedPriority) {
- suggested->SetActiveCore(cpu_core_id);
+ suggested->SetActiveCore(core_id);
priority_queue.ChangeCore(suggested_core, suggested, true);
IncrementScheduledCount(suggested);
break;
@@ -356,7 +652,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
}
// Get the next suggestion.
- suggested = priority_queue.GetSuggestedNext(cpu_core_id, suggested);
+ suggested = priority_queue.GetSuggestedNext(core_id, suggested);
}
}
}
@@ -365,71 +661,13 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
SetSchedulerUpdateNeeded(kernel);
}
-bool KScheduler::CanSchedule(KernelCore& kernel) {
- return kernel.GetCurrentEmuThread()->GetDisableDispatchCount() <= 1;
-}
-
-bool KScheduler::IsSchedulerUpdateNeeded(const KernelCore& kernel) {
- return kernel.GlobalSchedulerContext().scheduler_update_needed.load(std::memory_order_acquire);
-}
-
-void KScheduler::SetSchedulerUpdateNeeded(KernelCore& kernel) {
- kernel.GlobalSchedulerContext().scheduler_update_needed.store(true, std::memory_order_release);
-}
-
-void KScheduler::ClearSchedulerUpdateNeeded(KernelCore& kernel) {
- kernel.GlobalSchedulerContext().scheduler_update_needed.store(false, std::memory_order_release);
-}
-
-void KScheduler::DisableScheduling(KernelCore& kernel) {
- // If we are shutting down the kernel, none of this is relevant anymore.
- if (kernel.IsShuttingDown()) {
- return;
- }
-
- ASSERT(GetCurrentThreadPointer(kernel)->GetDisableDispatchCount() >= 0);
- GetCurrentThreadPointer(kernel)->DisableDispatch();
-}
-
-void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) {
- // If we are shutting down the kernel, none of this is relevant anymore.
- if (kernel.IsShuttingDown()) {
- return;
- }
-
- auto* current_thread = GetCurrentThreadPointer(kernel);
-
- ASSERT(current_thread->GetDisableDispatchCount() >= 1);
-
- if (current_thread->GetDisableDispatchCount() > 1) {
- current_thread->EnableDispatch();
- } else {
- RescheduleCores(kernel, cores_needing_scheduling);
- }
-
- // Special case to ensure dummy threads that are waiting block.
- current_thread->IfDummyThreadTryWait();
-}
-
-u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
- if (IsSchedulerUpdateNeeded(kernel)) {
- return UpdateHighestPriorityThreadsImpl(kernel);
- } else {
- return 0;
- }
-}
-
-KSchedulerPriorityQueue& KScheduler::GetPriorityQueue(KernelCore& kernel) {
- return kernel.GlobalSchedulerContext().priority_queue;
-}
-
void KScheduler::YieldWithoutCoreMigration(KernelCore& kernel) {
// Validate preconditions.
ASSERT(CanSchedule(kernel));
ASSERT(kernel.CurrentProcess() != nullptr);
// Get the current thread and process.
- KThread& cur_thread = Kernel::GetCurrentThread(kernel);
+ KThread& cur_thread = GetCurrentThread(kernel);
KProcess& cur_process = *kernel.CurrentProcess();
// If the thread's yield count matches, there's nothing for us to do.
@@ -442,7 +680,7 @@ void KScheduler::YieldWithoutCoreMigration(KernelCore& kernel) {
// Perform the yield.
{
- KScopedSchedulerLock lock(kernel);
+ KScopedSchedulerLock sl{kernel};
const auto cur_state = cur_thread.GetRawState();
if (cur_state == ThreadState::Runnable) {
@@ -468,7 +706,7 @@ void KScheduler::YieldWithCoreMigration(KernelCore& kernel) {
ASSERT(kernel.CurrentProcess() != nullptr);
// Get the current thread and process.
- KThread& cur_thread = Kernel::GetCurrentThread(kernel);
+ KThread& cur_thread = GetCurrentThread(kernel);
KProcess& cur_process = *kernel.CurrentProcess();
// If the thread's yield count matches, there's nothing for us to do.
@@ -481,7 +719,7 @@ void KScheduler::YieldWithCoreMigration(KernelCore& kernel) {
// Perform the yield.
{
- KScopedSchedulerLock lock(kernel);
+ KScopedSchedulerLock sl{kernel};
const auto cur_state = cur_thread.GetRawState();
if (cur_state == ThreadState::Runnable) {
@@ -501,7 +739,7 @@ void KScheduler::YieldWithCoreMigration(KernelCore& kernel) {
if (KThread* running_on_suggested_core =
(suggested_core >= 0)
- ? kernel.Scheduler(suggested_core).state.highest_priority_thread
+ ? kernel.Scheduler(suggested_core).m_state.highest_priority_thread
: nullptr;
running_on_suggested_core != suggested) {
// If the current thread's priority is higher than our suggestion's we prefer
@@ -556,7 +794,7 @@ void KScheduler::YieldToAnyThread(KernelCore& kernel) {
ASSERT(kernel.CurrentProcess() != nullptr);
// Get the current thread and process.
- KThread& cur_thread = Kernel::GetCurrentThread(kernel);
+ KThread& cur_thread = GetCurrentThread(kernel);
KProcess& cur_process = *kernel.CurrentProcess();
// If the thread's yield count matches, there's nothing for us to do.
@@ -569,7 +807,7 @@ void KScheduler::YieldToAnyThread(KernelCore& kernel) {
// Perform the yield.
{
- KScopedSchedulerLock lock(kernel);
+ KScopedSchedulerLock sl{kernel};
const auto cur_state = cur_thread.GetRawState();
if (cur_state == ThreadState::Runnable) {
@@ -626,219 +864,19 @@ void KScheduler::YieldToAnyThread(KernelCore& kernel) {
}
}
-KScheduler::KScheduler(Core::System& system_, s32 core_id_) : system{system_}, core_id{core_id_} {
- switch_fiber = std::make_shared<Common::Fiber>(OnSwitch, this);
- state.needs_scheduling.store(true);
- state.interrupt_task_thread_runnable = false;
- state.should_count_idle = false;
- state.idle_count = 0;
- state.idle_thread_stack = nullptr;
- state.highest_priority_thread = nullptr;
-}
-
-void KScheduler::Finalize() {
- if (idle_thread) {
- idle_thread->Close();
- idle_thread = nullptr;
- }
-}
-
-KScheduler::~KScheduler() {
- ASSERT(!idle_thread);
-}
-
-KThread* KScheduler::GetCurrentThread() const {
- if (auto result = current_thread.load(); result) {
- return result;
- }
- return idle_thread;
-}
-
-u64 KScheduler::GetLastContextSwitchTicks() const {
- return last_context_switch_time;
-}
-
-void KScheduler::RescheduleCurrentCore() {
- ASSERT(GetCurrentThread()->GetDisableDispatchCount() == 1);
-
- auto& phys_core = system.Kernel().PhysicalCore(core_id);
- if (phys_core.IsInterrupted()) {
- phys_core.ClearInterrupt();
+void KScheduler::RescheduleOtherCores(u64 cores_needing_scheduling) {
+ if (const u64 core_mask = cores_needing_scheduling & ~(1ULL << m_core_id); core_mask != 0) {
+ RescheduleCores(kernel, core_mask);
}
-
- guard.Lock();
- if (state.needs_scheduling.load()) {
- Schedule();
- } else {
- GetCurrentThread()->EnableDispatch();
- guard.Unlock();
- }
-}
-
-void KScheduler::OnThreadStart() {
- SwitchContextStep2();
}
-void KScheduler::Unload(KThread* thread) {
- ASSERT(thread);
-
- LOG_TRACE(Kernel, "core {}, unload thread {}", core_id, thread ? thread->GetName() : "nullptr");
-
- if (thread->IsCallingSvc()) {
- thread->ClearIsCallingSvc();
- }
-
- auto& physical_core = system.Kernel().PhysicalCore(core_id);
- if (!physical_core.IsInitialized()) {
- return;
- }
-
- Core::ARM_Interface& cpu_core = physical_core.ArmInterface();
- cpu_core.SaveContext(thread->GetContext32());
- cpu_core.SaveContext(thread->GetContext64());
- // Save the TPIDR_EL0 system register in case it was modified.
- thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
- cpu_core.ClearExclusiveState();
-
- if (!thread->IsTerminationRequested() && thread->GetActiveCore() == core_id) {
- prev_thread = thread;
- } else {
- prev_thread = nullptr;
- }
-
- thread->context_guard.Unlock();
-}
-
-void KScheduler::Reload(KThread* thread) {
- LOG_TRACE(Kernel, "core {}, reload thread {}", core_id, thread->GetName());
-
- Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
- cpu_core.LoadContext(thread->GetContext32());
- cpu_core.LoadContext(thread->GetContext64());
- cpu_core.SetTlsAddress(thread->GetTLSAddress());
- cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
- cpu_core.ClearExclusiveState();
-}
-
-void KScheduler::SwitchContextStep2() {
- // Load context of new thread
- Reload(GetCurrentThread());
-
- RescheduleCurrentCore();
-}
-
-void KScheduler::ScheduleImpl() {
- KThread* previous_thread = GetCurrentThread();
- KThread* next_thread = state.highest_priority_thread;
-
- state.needs_scheduling.store(false);
-
- // We never want to schedule a null thread, so use the idle thread if we don't have a next.
- if (next_thread == nullptr) {
- next_thread = idle_thread;
- }
-
- if (next_thread->GetCurrentCore() != core_id) {
- next_thread->SetCurrentCore(core_id);
- }
-
- // We never want to schedule a dummy thread, as these are only used by host threads for locking.
- if (next_thread->GetThreadType() == ThreadType::Dummy) {
- ASSERT_MSG(false, "Dummy threads should never be scheduled!");
- next_thread = idle_thread;
- }
-
- // If we're not actually switching thread, there's nothing to do.
- if (next_thread == current_thread.load()) {
- previous_thread->EnableDispatch();
- guard.Unlock();
- return;
- }
-
- // Update the CPU time tracking variables.
- KProcess* const previous_process = system.Kernel().CurrentProcess();
- UpdateLastContextSwitchTime(previous_thread, previous_process);
-
- // Save context for previous thread
- Unload(previous_thread);
-
- std::shared_ptr<Common::Fiber>* old_context;
- old_context = &previous_thread->GetHostContext();
-
- // Set the new thread.
- current_thread.store(next_thread);
-
- guard.Unlock();
-
- Common::Fiber::YieldTo(*old_context, *switch_fiber);
- /// When a thread wakes up, the scheduler may have changed to other in another core.
- auto& next_scheduler = *system.Kernel().CurrentScheduler();
- next_scheduler.SwitchContextStep2();
-}
-
-void KScheduler::OnSwitch(void* this_scheduler) {
- KScheduler* sched = static_cast<KScheduler*>(this_scheduler);
- sched->SwitchToCurrent();
-}
-
-void KScheduler::SwitchToCurrent() {
- while (true) {
- {
- KScopedSpinLock lk{guard};
- current_thread.store(state.highest_priority_thread);
- state.needs_scheduling.store(false);
+void KScheduler::RescheduleCores(KernelCore& kernel, u64 core_mask) {
+ // Send IPI
+ for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
+ if (core_mask & (1ULL << i)) {
+ kernel.PhysicalCore(i).Interrupt();
}
- const auto is_switch_pending = [this] {
- KScopedSpinLock lk{guard};
- return state.needs_scheduling.load();
- };
- do {
- auto next_thread = current_thread.load();
- if (next_thread != nullptr) {
- const auto locked = next_thread->context_guard.TryLock();
- if (state.needs_scheduling.load()) {
- next_thread->context_guard.Unlock();
- break;
- }
- if (next_thread->GetActiveCore() != core_id) {
- next_thread->context_guard.Unlock();
- break;
- }
- if (!locked) {
- continue;
- }
- }
- auto thread = next_thread ? next_thread : idle_thread;
- Common::Fiber::YieldTo(switch_fiber, *thread->GetHostContext());
- } while (!is_switch_pending());
}
}
-void KScheduler::UpdateLastContextSwitchTime(KThread* thread, KProcess* process) {
- const u64 prev_switch_ticks = last_context_switch_time;
- const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks();
- const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
-
- if (thread != nullptr) {
- thread->AddCpuTime(core_id, update_ticks);
- }
-
- if (process != nullptr) {
- process->UpdateCPUTimeTicks(update_ticks);
- }
-
- last_context_switch_time = most_recent_switch_ticks;
-}
-
-void KScheduler::Initialize() {
- idle_thread = KThread::Create(system.Kernel());
- ASSERT(KThread::InitializeIdleThread(system, idle_thread, core_id).IsSuccess());
- idle_thread->SetName(fmt::format("IdleThread:{}", core_id));
-}
-
-KScopedSchedulerLock::KScopedSchedulerLock(KernelCore& kernel)
- : KScopedLock(kernel.GlobalSchedulerContext().SchedulerLock()) {}
-
-KScopedSchedulerLock::~KScopedSchedulerLock() = default;
-
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scheduler.h b/src/core/hle/kernel/k_scheduler.h
index 82fcd99e7..534321d8d 100644
--- a/src/core/hle/kernel/k_scheduler.h
+++ b/src/core/hle/kernel/k_scheduler.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -12,6 +11,7 @@
#include "core/hle/kernel/k_scheduler_lock.h"
#include "core/hle/kernel/k_scoped_lock.h"
#include "core/hle/kernel/k_spin_lock.h"
+#include "core/hle/kernel/k_thread.h"
namespace Common {
class Fiber;
@@ -24,188 +24,150 @@ class System;
namespace Kernel {
class KernelCore;
+class KInterruptTaskManager;
class KProcess;
-class SchedulerLock;
class KThread;
+class KScopedDisableDispatch;
+class KScopedSchedulerLock;
+class KScopedSchedulerLockAndSleep;
class KScheduler final {
public:
- explicit KScheduler(Core::System& system_, s32 core_id_);
- ~KScheduler();
-
- void Finalize();
+ YUZU_NON_COPYABLE(KScheduler);
+ YUZU_NON_MOVEABLE(KScheduler);
- /// Reschedules to the next available thread (call after current thread is suspended)
- void RescheduleCurrentCore();
+ using LockType = KAbstractSchedulerLock<KScheduler>;
- /// Reschedules cores pending reschedule, to be called on EnableScheduling.
- static void RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule);
+ explicit KScheduler(KernelCore& kernel);
+ ~KScheduler();
- /// The next two are for SingleCore Only.
- /// Unload current thread before preempting core.
+ void Initialize(KThread* main_thread, KThread* idle_thread, s32 core_id);
+ void Activate();
+ void OnThreadStart();
void Unload(KThread* thread);
-
- /// Reload current thread after core preemption.
void Reload(KThread* thread);
- /// Gets the current running thread
- [[nodiscard]] KThread* GetCurrentThread() const;
+ void SetInterruptTaskRunnable();
+ void RequestScheduleOnInterrupt();
+ void PreemptSingleCore();
- /// Gets the idle thread
- [[nodiscard]] KThread* GetIdleThread() const {
- return idle_thread;
+ u64 GetIdleCount() {
+ return m_state.idle_count;
}
- /// Returns true if the scheduler is idle
- [[nodiscard]] bool IsIdle() const {
- return GetCurrentThread() == idle_thread;
+ KThread* GetIdleThread() const {
+ return m_idle_thread;
}
- /// Gets the timestamp for the last context switch in ticks.
- [[nodiscard]] u64 GetLastContextSwitchTicks() const;
-
- [[nodiscard]] bool ContextSwitchPending() const {
- return state.needs_scheduling.load(std::memory_order_relaxed);
+ bool IsIdle() const {
+ return m_current_thread.load() == m_idle_thread;
}
- void Initialize();
-
- void OnThreadStart();
+ KThread* GetPreviousThread() const {
+ return m_state.prev_thread;
+ }
- [[nodiscard]] std::shared_ptr<Common::Fiber>& ControlContext() {
- return switch_fiber;
+ KThread* GetSchedulerCurrentThread() const {
+ return m_current_thread.load();
}
- [[nodiscard]] const std::shared_ptr<Common::Fiber>& ControlContext() const {
- return switch_fiber;
+ s64 GetLastContextSwitchTime() const {
+ return m_last_context_switch_time;
}
- [[nodiscard]] u64 UpdateHighestPriorityThread(KThread* highest_thread);
+ // Static public API.
+ static bool CanSchedule(KernelCore& kernel) {
+ return GetCurrentThread(kernel).GetDisableDispatchCount() == 0;
+ }
+ static bool IsSchedulerLockedByCurrentThread(KernelCore& kernel) {
+ return kernel.GlobalSchedulerContext().scheduler_lock.IsLockedByCurrentThread();
+ }
- /**
- * Takes a thread and moves it to the back of the it's priority list.
- *
- * @note This operation can be redundant and no scheduling is changed if marked as so.
- */
- static void YieldWithoutCoreMigration(KernelCore& kernel);
+ static bool IsSchedulerUpdateNeeded(KernelCore& kernel) {
+ return kernel.GlobalSchedulerContext().scheduler_update_needed;
+ }
+ static void SetSchedulerUpdateNeeded(KernelCore& kernel) {
+ kernel.GlobalSchedulerContext().scheduler_update_needed = true;
+ }
+ static void ClearSchedulerUpdateNeeded(KernelCore& kernel) {
+ kernel.GlobalSchedulerContext().scheduler_update_needed = false;
+ }
- /**
- * Takes a thread and moves it to the back of the it's priority list.
- * Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or
- * a better priority than the next thread in the core.
- *
- * @note This operation can be redundant and no scheduling is changed if marked as so.
- */
- static void YieldWithCoreMigration(KernelCore& kernel);
+ static void DisableScheduling(KernelCore& kernel);
+ static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling);
- /**
- * Takes a thread and moves it out of the scheduling queue.
- * and into the suggested queue. If no thread can be scheduled afterwards in that core,
- * a suggested thread is obtained instead.
- *
- * @note This operation can be redundant and no scheduling is changed if marked as so.
- */
- static void YieldToAnyThread(KernelCore& kernel);
+ static u64 UpdateHighestPriorityThreads(KernelCore& kernel);
static void ClearPreviousThread(KernelCore& kernel, KThread* thread);
- /// Notify the scheduler a thread's status has changed.
static void OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state);
-
- /// Notify the scheduler a thread's priority has changed.
static void OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority);
-
- /// Notify the scheduler a thread's core and/or affinity mask has changed.
static void OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread,
const KAffinityMask& old_affinity, s32 old_core);
- static bool CanSchedule(KernelCore& kernel);
- static bool IsSchedulerUpdateNeeded(const KernelCore& kernel);
- static void SetSchedulerUpdateNeeded(KernelCore& kernel);
- static void ClearSchedulerUpdateNeeded(KernelCore& kernel);
- static void DisableScheduling(KernelCore& kernel);
- static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling);
- [[nodiscard]] static u64 UpdateHighestPriorityThreads(KernelCore& kernel);
+ static void RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 priority);
+ static void RescheduleCores(KernelCore& kernel, u64 cores_needing_scheduling);
+
+ static void YieldWithoutCoreMigration(KernelCore& kernel);
+ static void YieldWithCoreMigration(KernelCore& kernel);
+ static void YieldToAnyThread(KernelCore& kernel);
private:
- friend class GlobalSchedulerContext;
-
- /**
- * Takes care of selecting the new scheduled threads in three steps:
- *
- * 1. First a thread is selected from the top of the priority queue. If no thread
- * is obtained then we move to step two, else we are done.
- *
- * 2. Second we try to get a suggested thread that's not assigned to any core or
- * that is not the top thread in that core.
- *
- * 3. Third is no suggested thread is found, we do a second pass and pick a running
- * thread in another core and swap it with its current thread.
- *
- * returns the cores needing scheduling.
- */
- [[nodiscard]] static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel);
-
- [[nodiscard]] static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel);
-
- void RotateScheduledQueue(s32 cpu_core_id, s32 priority);
-
- void Schedule() {
- ASSERT(GetCurrentThread()->GetDisableDispatchCount() == 1);
- this->ScheduleImpl();
+ // Static private API.
+ static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel) {
+ return kernel.GlobalSchedulerContext().priority_queue;
}
+ static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel);
- /// Switches the CPU's active thread context to that of the specified thread
- void ScheduleImpl();
-
- /// When a thread wakes up, it must run this through it's new scheduler
- void SwitchContextStep2();
+ static void RescheduleCurrentHLEThread(KernelCore& kernel);
- /**
- * Called on every context switch to update the internal timestamp
- * This also updates the running time ticks for the given thread and
- * process using the following difference:
- *
- * ticks += most_recent_ticks - last_context_switch_ticks
- *
- * The internal tick timestamp for the scheduler is simply the
- * most recent tick count retrieved. No special arithmetic is
- * applied to it.
- */
- void UpdateLastContextSwitchTime(KThread* thread, KProcess* process);
+ // Instanced private API.
+ void ScheduleImpl();
+ void ScheduleImplFiber();
+ void SwitchThread(KThread* next_thread);
- static void OnSwitch(void* this_scheduler);
- void SwitchToCurrent();
+ void Schedule();
+ void ScheduleOnInterrupt();
- KThread* prev_thread{};
- std::atomic<KThread*> current_thread{};
+ void RescheduleOtherCores(u64 cores_needing_scheduling);
+ void RescheduleCurrentCore();
+ void RescheduleCurrentCoreImpl();
- KThread* idle_thread{};
+ u64 UpdateHighestPriorityThread(KThread* thread);
- std::shared_ptr<Common::Fiber> switch_fiber{};
+private:
+ friend class KScopedDisableDispatch;
struct SchedulingState {
- std::atomic<bool> needs_scheduling{};
- bool interrupt_task_thread_runnable{};
- bool should_count_idle{};
- u64 idle_count{};
- KThread* highest_priority_thread{};
- void* idle_thread_stack{};
+ std::atomic<bool> needs_scheduling{false};
+ bool interrupt_task_runnable{false};
+ bool should_count_idle{false};
+ u64 idle_count{0};
+ KThread* highest_priority_thread{nullptr};
+ void* idle_thread_stack{nullptr};
+ std::atomic<KThread*> prev_thread{nullptr};
+ KInterruptTaskManager* interrupt_task_manager{nullptr};
};
- SchedulingState state;
-
- Core::System& system;
- u64 last_context_switch_time{};
- const s32 core_id;
-
- KSpinLock guard{};
+ KernelCore& kernel;
+ SchedulingState m_state;
+ bool m_is_active{false};
+ s32 m_core_id{0};
+ s64 m_last_context_switch_time{0};
+ KThread* m_idle_thread{nullptr};
+ std::atomic<KThread*> m_current_thread{nullptr};
+
+ std::shared_ptr<Common::Fiber> m_switch_fiber{};
+ KThread* m_switch_cur_thread{};
+ KThread* m_switch_highest_priority_thread{};
+ bool m_switch_from_schedule{};
};
-class [[nodiscard]] KScopedSchedulerLock : KScopedLock<GlobalSchedulerContext::LockType> {
+class KScopedSchedulerLock : public KScopedLock<KScheduler::LockType> {
public:
- explicit KScopedSchedulerLock(KernelCore& kernel);
- ~KScopedSchedulerLock();
+ explicit KScopedSchedulerLock(KernelCore& kernel)
+ : KScopedLock(kernel.GlobalSchedulerContext().scheduler_lock) {}
+ ~KScopedSchedulerLock() = default;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scheduler_lock.h b/src/core/hle/kernel/k_scheduler_lock.h
index 93c47f1b1..73314b45e 100644
--- a/src/core/hle/kernel/k_scheduler_lock.h
+++ b/src/core/hle/kernel/k_scheduler_lock.h
@@ -1,13 +1,15 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include <atomic>
#include "common/assert.h"
+#include "core/hle/kernel/k_interrupt_manager.h"
#include "core/hle/kernel/k_spin_lock.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/physical_core.h"
namespace Kernel {
@@ -75,7 +77,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_scoped_lock.h b/src/core/hle/kernel/k_scoped_lock.h
index 89a7ffe49..857e21156 100644
--- a/src/core/hle/kernel/k_scoped_lock.h
+++ b/src/core/hle/kernel/k_scoped_lock.h
@@ -1,9 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-// This file references various implementation details from Atmosphere, an open-source firmware for
-// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_scoped_resource_reservation.h b/src/core/hle/kernel/k_scoped_resource_reservation.h
index 07272075d..436bcf9fe 100644
--- a/src/core/hle/kernel/k_scoped_resource_reservation.h
+++ b/src/core/hle/kernel/k_scoped_resource_reservation.h
@@ -1,9 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-// This file references various implementation details from Atmosphere, an open-source firmware for
-// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
index 2995c492d..76c095e69 100644
--- a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
+++ b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
@@ -1,9 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-// This file references various implementation details from Atmosphere, an open-source firmware for
-// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_server_port.cpp b/src/core/hle/kernel/k_server_port.cpp
index 433fc98e1..e968f26ad 100644
--- a/src/core/hle/kernel/k_server_port.cpp
+++ b/src/core/hle/kernel/k_server_port.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <tuple>
#include "common/assert.h"
@@ -62,6 +61,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_port.h b/src/core/hle/kernel/k_server_port.h
index 6302d5e61..fd4f4bd20 100644
--- a/src/core/hle/kernel/k_server_port.h
+++ b/src/core/hle/kernel/k_server_port.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -30,11 +29,11 @@ public:
/// Whether or not this server port has an HLE handler available.
bool HasSessionRequestHandler() const {
- return session_handler != nullptr;
+ return !session_handler.expired();
}
/// Gets the HLE handler for this port.
- SessionRequestHandlerPtr GetSessionRequestHandler() const {
+ SessionRequestHandlerWeakPtr GetSessionRequestHandler() const {
return session_handler;
}
@@ -42,7 +41,7 @@ public:
* Sets the HLE handler template for the port. ServerSessions crated by connecting to this port
* will inherit a reference to this handler.
*/
- void SetSessionHandler(SessionRequestHandlerPtr&& handler) {
+ void SetSessionHandler(SessionRequestHandlerWeakPtr&& handler) {
session_handler = std::move(handler);
}
@@ -66,7 +65,7 @@ private:
void CleanupSessions();
SessionList session_list;
- SessionRequestHandlerPtr session_handler;
+ SessionRequestHandlerWeakPtr session_handler;
KPort* parent{};
};
diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp
index 4d94eb9cf..802c646a6 100644
--- a/src/core/hle/kernel/k_server_session.cpp
+++ b/src/core/hle/kernel/k_server_session.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <tuple>
#include <utility>
@@ -27,10 +26,7 @@ namespace Kernel {
KServerSession::KServerSession(KernelCore& kernel_) : KSynchronizationObject{kernel_} {}
-KServerSession::~KServerSession() {
- // Ensure that the global list tracking server sessions does not hold on to a reference.
- kernel.UnregisterServerSession(this);
-}
+KServerSession::~KServerSession() = default;
void KServerSession::Initialize(KSession* parent_session_, std::string&& name_,
std::shared_ptr<SessionRequestManager> manager_) {
@@ -49,6 +45,12 @@ void KServerSession::Destroy() {
parent->OnServerClosed();
parent->Close();
+
+ // 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() {
@@ -77,7 +79,7 @@ std::size_t KServerSession::NumDomainRequestHandlers() const {
return manager->DomainHandlerCount();
}
-ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
+Result KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
if (!context.HasDomainMessageHeader()) {
return ResultSuccess;
}
@@ -95,10 +97,15 @@ ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& co
"object_id {} is too big! This probably means a recent service call "
"to {} needed to return a new interface!",
object_id, name);
- UNREACHABLE();
+ ASSERT(false);
return ResultSuccess; // Ignore error if asserts are off
}
- return manager->DomainHandler(object_id - 1)->HandleSyncRequest(*this, context);
+ if (auto strong_ptr = manager->DomainHandler(object_id - 1).lock()) {
+ return strong_ptr->HandleSyncRequest(*this, context);
+ } else {
+ ASSERT(false);
+ return ResultSuccess;
+ }
case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id);
@@ -116,7 +123,7 @@ ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& co
return ResultSuccess;
}
-ResultCode KServerSession::QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory) {
+Result KServerSession::QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory) {
u32* cmd_buf{reinterpret_cast<u32*>(memory.GetPointer(thread->GetTLSAddress()))};
auto context = std::make_shared<HLERequestContext>(kernel, memory, this, thread);
@@ -136,8 +143,8 @@ ResultCode KServerSession::QueueSyncRequest(KThread* thread, Core::Memory::Memor
return ResultSuccess;
}
-ResultCode KServerSession::CompleteSyncRequest(HLERequestContext& context) {
- ResultCode result = ResultSuccess;
+Result KServerSession::CompleteSyncRequest(HLERequestContext& context) {
+ Result result = ResultSuccess;
// If the session has been converted to a domain, handle the domain request
if (manager->HasSessionRequestHandler(context)) {
@@ -166,8 +173,8 @@ ResultCode KServerSession::CompleteSyncRequest(HLERequestContext& context) {
return result;
}
-ResultCode KServerSession::HandleSyncRequest(KThread* thread, Core::Memory::Memory& memory,
- Core::Timing::CoreTiming& core_timing) {
+Result KServerSession::HandleSyncRequest(KThread* thread, Core::Memory::Memory& memory,
+ Core::Timing::CoreTiming& core_timing) {
return QueueSyncRequest(thread, memory);
}
diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h
index 5b76bf17c..6d0821945 100644
--- a/src/core/hle/kernel/k_server_session.h
+++ b/src/core/hle/kernel/k_server_session.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -74,10 +73,10 @@ public:
* @param memory Memory context to handle the sync request under.
* @param core_timing Core timing context to schedule the request event under.
*
- * @returns ResultCode from the operation.
+ * @returns Result from the operation.
*/
- ResultCode HandleSyncRequest(KThread* thread, Core::Memory::Memory& memory,
- Core::Timing::CoreTiming& core_timing);
+ Result HandleSyncRequest(KThread* thread, Core::Memory::Memory& memory,
+ Core::Timing::CoreTiming& core_timing);
/// Adds a new domain request handler to the collection of request handlers within
/// this ServerSession instance.
@@ -104,14 +103,14 @@ public:
private:
/// Queues a sync request from the emulated application.
- ResultCode QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory);
+ Result QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory);
/// Completes a sync request from the emulated application.
- ResultCode CompleteSyncRequest(HLERequestContext& context);
+ Result CompleteSyncRequest(HLERequestContext& context);
/// Handles a SyncRequest to a domain, forwarding the request to the proper object or closing an
/// object handle.
- ResultCode HandleDomainSyncRequest(Kernel::HLERequestContext& context);
+ Result HandleDomainSyncRequest(Kernel::HLERequestContext& context);
/// This session's HLE request handlers
std::shared_ptr<SessionRequestManager> manager;
diff --git a/src/core/hle/kernel/k_session.cpp b/src/core/hle/kernel/k_session.cpp
index a64b56b9e..ee05aa282 100644
--- a/src/core/hle/kernel/k_session.cpp
+++ b/src/core/hle/kernel/k_session.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_client_port.h"
#include "core/hle/kernel/k_client_session.h"
diff --git a/src/core/hle/kernel/k_session.h b/src/core/hle/kernel/k_session.h
index 62c328a68..c6ead403b 100644
--- a/src/core/hle/kernel/k_session.h
+++ b/src/core/hle/kernel/k_session.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp
index 51d7538ca..8ff1545b6 100644
--- a/src/core/hle/kernel/k_shared_memory.cpp
+++ b/src/core/hle/kernel/k_shared_memory.cpp
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "core/core.h"
@@ -18,12 +17,10 @@ KSharedMemory::~KSharedMemory() {
kernel.GetSystemResourceLimit()->Release(LimitableResource::PhysicalMemory, size);
}
-ResultCode KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_,
- KPageLinkedList&& page_list_,
- Svc::MemoryPermission owner_permission_,
- Svc::MemoryPermission user_permission_,
- PAddr physical_address_, std::size_t size_,
- std::string name_) {
+Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_,
+ KPageGroup&& page_list_, Svc::MemoryPermission owner_permission_,
+ Svc::MemoryPermission user_permission_, PAddr physical_address_,
+ std::size_t size_, std::string name_) {
// Set members.
owner_process = owner_process_;
device_memory = &device_memory_;
@@ -67,8 +64,8 @@ void KSharedMemory::Finalize() {
KAutoObjectWithSlabHeapAndContainer<KSharedMemory, KAutoObjectWithList>::Finalize();
}
-ResultCode KSharedMemory::Map(KProcess& target_process, VAddr address, std::size_t map_size,
- Svc::MemoryPermission permissions) {
+Result KSharedMemory::Map(KProcess& target_process, VAddr address, std::size_t map_size,
+ Svc::MemoryPermission permissions) {
const u64 page_count{(map_size + PageSize - 1) / PageSize};
if (page_list.GetNumPages() != page_count) {
@@ -86,7 +83,7 @@ ResultCode KSharedMemory::Map(KProcess& target_process, VAddr address, std::size
ConvertToKMemoryPermission(permissions));
}
-ResultCode KSharedMemory::Unmap(KProcess& target_process, VAddr address, std::size_t unmap_size) {
+Result KSharedMemory::Unmap(KProcess& target_process, VAddr address, std::size_t unmap_size) {
const u64 page_count{(unmap_size + PageSize - 1) / PageSize};
if (page_list.GetNumPages() != page_count) {
diff --git a/src/core/hle/kernel/k_shared_memory.h b/src/core/hle/kernel/k_shared_memory.h
index 81de36136..34cb98456 100644
--- a/src/core/hle/kernel/k_shared_memory.h
+++ b/src/core/hle/kernel/k_shared_memory.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -9,7 +8,7 @@
#include "common/common_types.h"
#include "core/device_memory.h"
#include "core/hle/kernel/k_memory_block.h"
-#include "core/hle/kernel/k_page_linked_list.h"
+#include "core/hle/kernel/k_page_group.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/result.h"
@@ -26,10 +25,10 @@ public:
explicit KSharedMemory(KernelCore& kernel_);
~KSharedMemory() override;
- ResultCode Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_,
- KPageLinkedList&& page_list_, Svc::MemoryPermission owner_permission_,
- Svc::MemoryPermission user_permission_, PAddr physical_address_,
- std::size_t size_, std::string name_);
+ Result Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_,
+ KPageGroup&& page_list_, Svc::MemoryPermission owner_permission_,
+ Svc::MemoryPermission user_permission_, PAddr physical_address_,
+ std::size_t size_, std::string name_);
/**
* Maps a shared memory block to an address in the target process' address space
@@ -38,8 +37,8 @@ public:
* @param map_size Size of the shared memory block to map
* @param permissions Memory block map permissions (specified by SVC field)
*/
- ResultCode Map(KProcess& target_process, VAddr address, std::size_t map_size,
- Svc::MemoryPermission permissions);
+ Result Map(KProcess& target_process, VAddr address, std::size_t map_size,
+ Svc::MemoryPermission permissions);
/**
* Unmaps a shared memory block from an address in the target process' address space
@@ -47,7 +46,7 @@ public:
* @param address Address in system memory to unmap shared memory block
* @param unmap_size Size of the shared memory block to unmap
*/
- ResultCode Unmap(KProcess& target_process, VAddr address, std::size_t unmap_size);
+ Result Unmap(KProcess& target_process, VAddr address, std::size_t unmap_size);
/**
* Gets a pointer to the shared memory block
@@ -77,7 +76,7 @@ public:
private:
Core::DeviceMemory* device_memory;
KProcess* owner_process{};
- KPageLinkedList page_list;
+ KPageGroup page_list;
Svc::MemoryPermission owner_permission{};
Svc::MemoryPermission user_permission{};
PAddr physical_address{};
diff --git a/src/core/hle/kernel/k_shared_memory_info.h b/src/core/hle/kernel/k_shared_memory_info.h
index 20bc19f46..e43db8515 100644
--- a/src/core/hle/kernel/k_shared_memory_info.h
+++ b/src/core/hle/kernel/k_shared_memory_info.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_slab_heap.h b/src/core/hle/kernel/k_slab_heap.h
index 05c0bec9c..2b303537e 100644
--- a/src/core/hle/kernel/k_slab_heap.h
+++ b/src/core/hle/kernel/k_slab_heap.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -16,39 +15,34 @@ class KernelCore;
namespace impl {
-class KSlabHeapImpl final {
-public:
+class KSlabHeapImpl {
YUZU_NON_COPYABLE(KSlabHeapImpl);
YUZU_NON_MOVEABLE(KSlabHeapImpl);
+public:
struct Node {
Node* next{};
};
+public:
constexpr KSlabHeapImpl() = default;
- constexpr ~KSlabHeapImpl() = default;
- void Initialize(std::size_t size) {
- ASSERT(head == nullptr);
- obj_size = size;
- }
-
- constexpr std::size_t GetObjectSize() const {
- return obj_size;
+ void Initialize() {
+ ASSERT(m_head == nullptr);
}
Node* GetHead() const {
- return head;
+ return m_head;
}
void* Allocate() {
- Node* ret = head.load();
+ Node* ret = m_head.load();
do {
if (ret == nullptr) {
break;
}
- } while (!head.compare_exchange_weak(ret, ret->next));
+ } while (!m_head.compare_exchange_weak(ret, ret->next));
return ret;
}
@@ -56,170 +50,157 @@ public:
void Free(void* obj) {
Node* node = static_cast<Node*>(obj);
- Node* cur_head = head.load();
+ Node* cur_head = m_head.load();
do {
node->next = cur_head;
- } while (!head.compare_exchange_weak(cur_head, node));
+ } while (!m_head.compare_exchange_weak(cur_head, node));
}
private:
- std::atomic<Node*> head{};
- std::size_t obj_size{};
+ std::atomic<Node*> m_head{};
};
} // namespace impl
-class KSlabHeapBase {
-public:
+template <bool SupportDynamicExpansion>
+class KSlabHeapBase : protected impl::KSlabHeapImpl {
YUZU_NON_COPYABLE(KSlabHeapBase);
YUZU_NON_MOVEABLE(KSlabHeapBase);
- constexpr KSlabHeapBase() = default;
- constexpr ~KSlabHeapBase() = default;
+private:
+ size_t m_obj_size{};
+ uintptr_t m_peak{};
+ uintptr_t m_start{};
+ uintptr_t m_end{};
- constexpr bool Contains(uintptr_t addr) const {
- return start <= addr && addr < end;
- }
+private:
+ void UpdatePeakImpl(uintptr_t obj) {
+ static_assert(std::atomic_ref<uintptr_t>::is_always_lock_free);
+ std::atomic_ref<uintptr_t> peak_ref(m_peak);
- constexpr std::size_t GetSlabHeapSize() const {
- return (end - start) / GetObjectSize();
+ const uintptr_t alloc_peak = obj + this->GetObjectSize();
+ uintptr_t cur_peak = m_peak;
+ do {
+ if (alloc_peak <= cur_peak) {
+ break;
+ }
+ } while (!peak_ref.compare_exchange_strong(cur_peak, alloc_peak));
}
- constexpr std::size_t GetObjectSize() const {
- return impl.GetObjectSize();
- }
+public:
+ constexpr KSlabHeapBase() = default;
- constexpr uintptr_t GetSlabHeapAddress() const {
- return start;
+ bool Contains(uintptr_t address) const {
+ return m_start <= address && address < m_end;
}
- std::size_t GetObjectIndexImpl(const void* obj) const {
- return (reinterpret_cast<uintptr_t>(obj) - start) / GetObjectSize();
+ void Initialize(size_t obj_size, void* memory, size_t memory_size) {
+ // Ensure we don't initialize a slab using null memory.
+ ASSERT(memory != nullptr);
+
+ // Set our object size.
+ m_obj_size = obj_size;
+
+ // Initialize the base allocator.
+ KSlabHeapImpl::Initialize();
+
+ // Set our tracking variables.
+ const size_t num_obj = (memory_size / obj_size);
+ m_start = reinterpret_cast<uintptr_t>(memory);
+ m_end = m_start + num_obj * obj_size;
+ m_peak = m_start;
+
+ // Free the objects.
+ u8* cur = reinterpret_cast<u8*>(m_end);
+
+ for (size_t i = 0; i < num_obj; i++) {
+ cur -= obj_size;
+ KSlabHeapImpl::Free(cur);
+ }
}
- std::size_t GetPeakIndex() const {
- return GetObjectIndexImpl(reinterpret_cast<const void*>(peak));
+ size_t GetSlabHeapSize() const {
+ return (m_end - m_start) / this->GetObjectSize();
}
- void* AllocateImpl() {
- return impl.Allocate();
+ size_t GetObjectSize() const {
+ return m_obj_size;
}
- void FreeImpl(void* obj) {
- // Don't allow freeing an object that wasn't allocated from this heap
- ASSERT(Contains(reinterpret_cast<uintptr_t>(obj)));
+ void* Allocate() {
+ void* obj = KSlabHeapImpl::Allocate();
- impl.Free(obj);
+ return obj;
}
- void InitializeImpl(std::size_t obj_size, void* memory, std::size_t memory_size) {
- // Ensure we don't initialize a slab using null memory
- ASSERT(memory != nullptr);
-
- // Initialize the base allocator
- impl.Initialize(obj_size);
+ void Free(void* obj) {
+ // Don't allow freeing an object that wasn't allocated from this heap.
+ const bool contained = this->Contains(reinterpret_cast<uintptr_t>(obj));
+ ASSERT(contained);
+ KSlabHeapImpl::Free(obj);
+ }
- // Set our tracking variables
- const std::size_t num_obj = (memory_size / obj_size);
- start = reinterpret_cast<uintptr_t>(memory);
- end = start + num_obj * obj_size;
- peak = start;
+ size_t GetObjectIndex(const void* obj) const {
+ if constexpr (SupportDynamicExpansion) {
+ if (!this->Contains(reinterpret_cast<uintptr_t>(obj))) {
+ return std::numeric_limits<size_t>::max();
+ }
+ }
- // Free the objects
- u8* cur = reinterpret_cast<u8*>(end);
+ return (reinterpret_cast<uintptr_t>(obj) - m_start) / this->GetObjectSize();
+ }
- for (std::size_t i{}; i < num_obj; i++) {
- cur -= obj_size;
- impl.Free(cur);
- }
+ size_t GetPeakIndex() const {
+ return this->GetObjectIndex(reinterpret_cast<const void*>(m_peak));
}
-private:
- using Impl = impl::KSlabHeapImpl;
+ uintptr_t GetSlabHeapAddress() const {
+ return m_start;
+ }
- Impl impl;
- uintptr_t peak{};
- uintptr_t start{};
- uintptr_t end{};
+ size_t GetNumRemaining() const {
+ // Only calculate the number of remaining objects under debug configuration.
+ return 0;
+ }
};
template <typename T>
-class KSlabHeap final : public KSlabHeapBase {
-public:
- enum class AllocationType {
- Host,
- Guest,
- };
+class KSlabHeap final : public KSlabHeapBase<false> {
+private:
+ using BaseHeap = KSlabHeapBase<false>;
- explicit constexpr KSlabHeap(AllocationType allocation_type_ = AllocationType::Host)
- : KSlabHeapBase(), allocation_type{allocation_type_} {}
+public:
+ constexpr KSlabHeap() = default;
- void Initialize(void* memory, std::size_t memory_size) {
- if (allocation_type == AllocationType::Guest) {
- InitializeImpl(sizeof(T), memory, memory_size);
- }
+ void Initialize(void* memory, size_t memory_size) {
+ BaseHeap::Initialize(sizeof(T), memory, memory_size);
}
T* Allocate() {
- switch (allocation_type) {
- case AllocationType::Host:
- // Fallback for cases where we do not yet support allocating guest memory from the slab
- // heap, such as for kernel memory regions.
- return new T;
-
- case AllocationType::Guest:
- T* obj = static_cast<T*>(AllocateImpl());
- if (obj != nullptr) {
- new (obj) T();
- }
- return obj;
- }
+ T* obj = static_cast<T*>(BaseHeap::Allocate());
- UNREACHABLE_MSG("Invalid AllocationType {}", allocation_type);
- return nullptr;
+ if (obj != nullptr) [[likely]] {
+ std::construct_at(obj);
+ }
+ return obj;
}
- T* AllocateWithKernel(KernelCore& kernel) {
- switch (allocation_type) {
- case AllocationType::Host:
- // Fallback for cases where we do not yet support allocating guest memory from the slab
- // heap, such as for kernel memory regions.
- return new T(kernel);
+ T* Allocate(KernelCore& kernel) {
+ T* obj = static_cast<T*>(BaseHeap::Allocate());
- case AllocationType::Guest:
- T* obj = static_cast<T*>(AllocateImpl());
- if (obj != nullptr) {
- new (obj) T(kernel);
- }
- return obj;
+ if (obj != nullptr) [[likely]] {
+ std::construct_at(obj, kernel);
}
-
- UNREACHABLE_MSG("Invalid AllocationType {}", allocation_type);
- return nullptr;
+ return obj;
}
void Free(T* obj) {
- switch (allocation_type) {
- case AllocationType::Host:
- // Fallback for cases where we do not yet support allocating guest memory from the slab
- // heap, such as for kernel memory regions.
- delete obj;
- return;
-
- case AllocationType::Guest:
- FreeImpl(obj);
- return;
- }
-
- UNREACHABLE_MSG("Invalid AllocationType {}", allocation_type);
+ BaseHeap::Free(obj);
}
- constexpr std::size_t GetObjectIndex(const T* obj) const {
- return GetObjectIndexImpl(obj);
+ size_t GetObjectIndex(const T* obj) const {
+ return BaseHeap::GetObjectIndex(obj);
}
-
-private:
- const AllocationType allocation_type;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_spin_lock.cpp b/src/core/hle/kernel/k_spin_lock.cpp
index 4412aa4bb..6e16a1849 100644
--- a/src/core/hle/kernel/k_spin_lock.cpp
+++ b/src/core/hle/kernel/k_spin_lock.cpp
@@ -1,54 +1,20 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#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..397a93d21 100644
--- a/src/core/hle/kernel/k_spin_lock.h
+++ b/src/core/hle/kernel/k_spin_lock.h
@@ -1,10 +1,9 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <atomic>
+#include <mutex>
#include "core/hle/kernel/k_scoped_lock.h"
@@ -25,7 +24,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_synchronization_object.cpp b/src/core/hle/kernel/k_synchronization_object.cpp
index e4c5eb74f..802dca046 100644
--- a/src/core/hle/kernel/k_synchronization_object.cpp
+++ b/src/core/hle/kernel/k_synchronization_object.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/common_types.h"
@@ -23,7 +22,7 @@ public:
: KThreadQueueWithoutEndWait(kernel_), m_objects(o), m_nodes(n), m_count(c) {}
void NotifyAvailable(KThread* waiting_thread, KSynchronizationObject* signaled_object,
- ResultCode wait_result) override {
+ Result wait_result) override {
// Determine the sync index, and unlink all nodes.
s32 sync_index = -1;
for (auto i = 0; i < m_count; ++i) {
@@ -46,8 +45,7 @@ public:
KThreadQueue::EndWait(waiting_thread, wait_result);
}
- void CancelWait(KThread* waiting_thread, ResultCode wait_result,
- bool cancel_timer_task) override {
+ void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override {
// Remove all nodes from our list.
for (auto i = 0; i < m_count; ++i) {
m_objects[i]->UnlinkNode(std::addressof(m_nodes[i]));
@@ -73,9 +71,9 @@ void KSynchronizationObject::Finalize() {
KAutoObject::Finalize();
}
-ResultCode KSynchronizationObject::Wait(KernelCore& kernel_ctx, s32* out_index,
- KSynchronizationObject** objects, const s32 num_objects,
- s64 timeout) {
+Result KSynchronizationObject::Wait(KernelCore& kernel_ctx, s32* out_index,
+ KSynchronizationObject** objects, const s32 num_objects,
+ s64 timeout) {
// Allocate space on stack for thread nodes.
std::vector<ThreadListNode> thread_nodes(num_objects);
@@ -149,7 +147,7 @@ KSynchronizationObject::KSynchronizationObject(KernelCore& kernel_)
KSynchronizationObject::~KSynchronizationObject() = default;
-void KSynchronizationObject::NotifyAvailable(ResultCode result) {
+void KSynchronizationObject::NotifyAvailable(Result result) {
KScopedSchedulerLock sl(kernel);
// If we're not signaled, we've nothing to notify.
diff --git a/src/core/hle/kernel/k_synchronization_object.h b/src/core/hle/kernel/k_synchronization_object.h
index ec235437b..8d8122ab7 100644
--- a/src/core/hle/kernel/k_synchronization_object.h
+++ b/src/core/hle/kernel/k_synchronization_object.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -25,9 +24,9 @@ public:
KThread* thread{};
};
- [[nodiscard]] static ResultCode Wait(KernelCore& kernel, s32* out_index,
- KSynchronizationObject** objects, const s32 num_objects,
- s64 timeout);
+ [[nodiscard]] static Result Wait(KernelCore& kernel, s32* out_index,
+ KSynchronizationObject** objects, const s32 num_objects,
+ s64 timeout);
void Finalize() override;
@@ -73,7 +72,7 @@ protected:
virtual void OnFinalizeSynchronizationObject() {}
- void NotifyAvailable(ResultCode result);
+ void NotifyAvailable(Result result);
void NotifyAvailable() {
return this->NotifyAvailable(ResultSuccess);
}
diff --git a/src/core/hle/kernel/k_system_control.h b/src/core/hle/kernel/k_system_control.h
index d755082c2..b8292e9d0 100644
--- a/src/core/hle/kernel/k_system_control.h
+++ b/src/core/hle/kernel/k_system_control.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp
index de3ffe0c7..174afc80d 100644
--- a/src/core/hle/kernel/k_thread.cpp
+++ b/src/core/hle/kernel/k_thread.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <atomic>
@@ -14,9 +13,7 @@
#include "common/common_types.h"
#include "common/fiber.h"
#include "common/logging/log.h"
-#include "common/scope_exit.h"
#include "common/settings.h"
-#include "common/thread_queue_list.h"
#include "core/core.h"
#include "core/cpu_manager.h"
#include "core/hardware_properties.h"
@@ -33,7 +30,6 @@
#include "core/hle/kernel/k_worker_task_manager.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"
-#include "core/hle/kernel/time_manager.h"
#include "core/hle/result.h"
#include "core/memory.h"
@@ -84,8 +80,7 @@ public:
explicit ThreadQueueImplForKThreadSetProperty(KernelCore& kernel_, KThread::WaiterList* wl)
: KThreadQueue(kernel_), m_wait_list(wl) {}
- void CancelWait(KThread* waiting_thread, ResultCode wait_result,
- bool cancel_timer_task) override {
+ void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override {
// Remove the thread from the wait list.
m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread));
@@ -103,8 +98,8 @@ KThread::KThread(KernelCore& kernel_)
: KAutoObjectWithSlabHeapAndContainer{kernel_}, activity_pause_lock{kernel_} {}
KThread::~KThread() = default;
-ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_stack_top, s32 prio,
- s32 virt_core, KProcess* owner, ThreadType type) {
+Result KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_stack_top, s32 prio,
+ s32 virt_core, KProcess* owner, ThreadType type) {
// Assert parameters are valid.
ASSERT((type == ThreadType::Main) || (type == ThreadType::Dummy) ||
(Svc::HighestThreadPriority <= prio && prio <= Svc::LowestThreadPriority));
@@ -137,7 +132,7 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s
UNIMPLEMENTED();
break;
default:
- UNREACHABLE_MSG("KThread::Initialize: Unknown ThreadType {}", static_cast<u32>(type));
+ ASSERT_MSG(false, "KThread::Initialize: Unknown ThreadType {}", static_cast<u32>(type));
break;
}
thread_type = type;
@@ -202,6 +197,10 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s
resource_limit_release_hint = false;
cpu_time = 0;
+ // Set debug context.
+ stack_top = user_stack_top;
+ argument = arg;
+
// Clear our stack parameters.
std::memset(static_cast<void*>(std::addressof(GetStackParameters())), 0,
sizeof(StackParameters));
@@ -210,7 +209,7 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s
if (owner != nullptr) {
// Setup the TLS, if needed.
if (type == ThreadType::User) {
- tls_address = owner->CreateTLSRegion();
+ R_TRY(owner->CreateThreadLocalRegion(std::addressof(tls_address)));
}
parent = owner;
@@ -225,7 +224,7 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s
// Setup the stack parameters.
StackParameters& sp = GetStackParameters();
sp.cur_thread = this;
- sp.disable_count = 0;
+ sp.disable_count = 1;
SetInExceptionHandler();
// Set thread ID.
@@ -245,46 +244,51 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s
return ResultSuccess;
}
-ResultCode KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_t arg,
- VAddr user_stack_top, s32 prio, s32 core, KProcess* owner,
- ThreadType type, std::function<void(void*)>&& init_func,
- void* init_func_parameter) {
+Result KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_t arg,
+ VAddr user_stack_top, s32 prio, s32 core, KProcess* owner,
+ ThreadType type, std::function<void()>&& init_func) {
// Initialize the thread.
R_TRY(thread->Initialize(func, arg, user_stack_top, prio, core, owner, type));
// Initialize emulation parameters.
- thread->host_context =
- std::make_shared<Common::Fiber>(std::move(init_func), init_func_parameter);
+ thread->host_context = std::make_shared<Common::Fiber>(std::move(init_func));
thread->is_single_core = !Settings::values.use_multi_core.GetValue();
return ResultSuccess;
}
-ResultCode KThread::InitializeDummyThread(KThread* thread) {
- return thread->Initialize({}, {}, {}, DummyThreadPriority, 3, {}, ThreadType::Dummy);
+Result KThread::InitializeDummyThread(KThread* thread) {
+ // Initialize the thread.
+ R_TRY(thread->Initialize({}, {}, {}, DummyThreadPriority, 3, {}, ThreadType::Dummy));
+
+ // Initialize emulation parameters.
+ thread->stack_parameters.disable_count = 0;
+
+ return ResultSuccess;
+}
+
+Result KThread::InitializeMainThread(Core::System& system, KThread* thread, s32 virt_core) {
+ return InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, ThreadType::Main,
+ system.GetCpuManager().GetGuestActivateFunc());
}
-ResultCode KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) {
+Result KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) {
return InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, ThreadType::Main,
- Core::CpuManager::GetIdleThreadStartFunc(),
- system.GetCpuManager().GetStartFuncParamater());
+ system.GetCpuManager().GetIdleThreadStartFunc());
}
-ResultCode KThread::InitializeHighPriorityThread(Core::System& system, KThread* thread,
- KThreadFunction func, uintptr_t arg,
- s32 virt_core) {
+Result KThread::InitializeHighPriorityThread(Core::System& system, KThread* thread,
+ KThreadFunction func, uintptr_t arg, s32 virt_core) {
return InitializeThread(thread, func, arg, {}, {}, virt_core, nullptr, ThreadType::HighPriority,
- Core::CpuManager::GetSuspendThreadStartFunc(),
- system.GetCpuManager().GetStartFuncParamater());
+ system.GetCpuManager().GetShutdownThreadStartFunc());
}
-ResultCode KThread::InitializeUserThread(Core::System& system, KThread* thread,
- KThreadFunction func, uintptr_t arg, VAddr user_stack_top,
- s32 prio, s32 virt_core, KProcess* owner) {
+Result KThread::InitializeUserThread(Core::System& system, KThread* thread, KThreadFunction func,
+ uintptr_t arg, VAddr user_stack_top, s32 prio, s32 virt_core,
+ KProcess* owner) {
system.Kernel().GlobalSchedulerContext().AddThread(thread);
return InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner,
- ThreadType::User, Core::CpuManager::GetGuestThreadStartFunc(),
- system.GetCpuManager().GetStartFuncParamater());
+ ThreadType::User, system.GetCpuManager().GetGuestThreadFunc());
}
void KThread::PostDestroy(uintptr_t arg) {
@@ -305,7 +309,7 @@ void KThread::Finalize() {
// If the thread has a local region, delete it.
if (tls_address != 0) {
- parent->FreeTLSRegion(tls_address);
+ ASSERT(parent->DeleteThreadLocalRegion(tls_address).IsSuccess());
}
// Release any waiters.
@@ -315,17 +319,26 @@ void KThread::Finalize() {
auto it = waiter_list.begin();
while (it != waiter_list.end()) {
- // Clear the lock owner
- it->SetLockOwner(nullptr);
+ // Get the thread.
+ KThread* const waiter = std::addressof(*it);
+
+ // The thread shouldn't be a kernel waiter.
+ ASSERT(!IsKernelAddressKey(waiter->GetAddressKey()));
+
+ // Clear the lock owner.
+ waiter->SetLockOwner(nullptr);
// Erase the waiter from our list.
it = waiter_list.erase(it);
// Cancel the thread's wait.
- it->CancelWait(ResultInvalidState, true);
+ waiter->CancelWait(ResultInvalidState, true);
}
}
+ // Release host emulation members.
+ host_context.reset();
+
// Perform inherited finalization.
KSynchronizationObject::Finalize();
}
@@ -379,7 +392,7 @@ void KThread::FinishTermination() {
for (std::size_t i = 0; i < static_cast<std::size_t>(Core::Hardware::NUM_CPU_CORES); ++i) {
KThread* core_thread{};
do {
- core_thread = kernel.Scheduler(i).GetCurrentThread();
+ core_thread = kernel.Scheduler(i).GetSchedulerCurrentThread();
} while (core_thread == this);
}
}
@@ -484,9 +497,7 @@ void KThread::Unpin() {
// Resume any threads that began waiting on us while we were pinned.
for (auto it = pinned_waiter_list.begin(); it != pinned_waiter_list.end(); ++it) {
- if (it->GetState() == ThreadState::Waiting) {
- it->SetState(ThreadState::Runnable);
- }
+ it->EndWait(ResultSuccess);
}
}
@@ -520,7 +531,7 @@ void KThread::ClearInterruptFlag() {
memory.Write16(tls_address + offsetof(ThreadLocalRegion, interrupt_flag), 0);
}
-ResultCode KThread::GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask) {
+Result KThread::GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask) {
KScopedSchedulerLock sl{kernel};
// Get the virtual mask.
@@ -530,7 +541,7 @@ ResultCode KThread::GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask) {
return ResultSuccess;
}
-ResultCode KThread::GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask) {
+Result KThread::GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask) {
KScopedSchedulerLock sl{kernel};
ASSERT(num_core_migration_disables >= 0);
@@ -546,7 +557,7 @@ ResultCode KThread::GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_m
return ResultSuccess;
}
-ResultCode KThread::SetCoreMask(s32 core_id_, u64 v_affinity_mask) {
+Result KThread::SetCoreMask(s32 core_id_, u64 v_affinity_mask) {
ASSERT(parent != nullptr);
ASSERT(v_affinity_mask != 0);
KScopedLightLock lk(activity_pause_lock);
@@ -628,7 +639,7 @@ ResultCode KThread::SetCoreMask(s32 core_id_, u64 v_affinity_mask) {
s32 thread_core;
for (thread_core = 0; thread_core < static_cast<s32>(Core::Hardware::NUM_CPU_CORES);
++thread_core) {
- if (kernel.Scheduler(thread_core).GetCurrentThread() == this) {
+ if (kernel.Scheduler(thread_core).GetSchedulerCurrentThread() == this) {
thread_is_current = true;
break;
}
@@ -723,10 +734,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,14 +749,27 @@ 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);
}
-ResultCode KThread::SetActivity(Svc::ThreadActivity activity) {
+void KThread::WaitUntilSuspended() {
+ // Make sure we have a suspend requested.
+ ASSERT(IsSuspendRequested());
+
+ // Loop until the thread is not executing on any core.
+ for (std::size_t i = 0; i < static_cast<std::size_t>(Core::Hardware::NUM_CPU_CORES); ++i) {
+ KThread* core_thread{};
+ do {
+ core_thread = kernel.Scheduler(i).GetSchedulerCurrentThread();
+ } while (core_thread == this);
+ }
+}
+
+Result KThread::SetActivity(Svc::ThreadActivity activity) {
// Lock ourselves.
KScopedLightLock lk(activity_pause_lock);
@@ -806,7 +830,7 @@ ResultCode KThread::SetActivity(Svc::ThreadActivity activity) {
// Check if the thread is currently running.
// If it is, we'll need to retry.
for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) {
- if (kernel.Scheduler(i).GetCurrentThread() == this) {
+ if (kernel.Scheduler(i).GetSchedulerCurrentThread() == this) {
thread_is_current = true;
break;
}
@@ -818,7 +842,7 @@ ResultCode KThread::SetActivity(Svc::ThreadActivity activity) {
return ResultSuccess;
}
-ResultCode KThread::GetThreadContext3(std::vector<u8>& out) {
+Result KThread::GetThreadContext3(std::vector<u8>& out) {
// Lock ourselves.
KScopedLightLock lk{activity_pause_lock};
@@ -868,6 +892,7 @@ void KThread::AddWaiterImpl(KThread* thread) {
// Keep track of how many kernel waiters we have.
if (IsKernelAddressKey(thread->GetAddressKey())) {
ASSERT((num_kernel_waiters++) >= 0);
+ KScheduler::SetSchedulerUpdateNeeded(kernel);
}
// Insert the waiter.
@@ -881,6 +906,7 @@ void KThread::RemoveWaiterImpl(KThread* thread) {
// Keep track of how many kernel waiters we have.
if (IsKernelAddressKey(thread->GetAddressKey())) {
ASSERT((num_kernel_waiters--) > 0);
+ KScheduler::SetSchedulerUpdateNeeded(kernel);
}
// Remove the waiter.
@@ -956,6 +982,7 @@ KThread* KThread::RemoveWaiterByKey(s32* out_num_waiters, VAddr key) {
// Keep track of how many kernel waiters we have.
if (IsKernelAddressKey(thread->GetAddressKey())) {
ASSERT((num_kernel_waiters--) > 0);
+ KScheduler::SetSchedulerUpdateNeeded(kernel);
}
it = waiter_list.erase(it);
@@ -983,7 +1010,7 @@ KThread* KThread::RemoveWaiterByKey(s32* out_num_waiters, VAddr key) {
return next_lock_owner;
}
-ResultCode KThread::Run() {
+Result KThread::Run() {
while (true) {
KScopedSchedulerLock lk{kernel};
@@ -1011,8 +1038,6 @@ ResultCode KThread::Run() {
// Set our state and finish.
SetState(ThreadState::Runnable);
- DisableDispatch();
-
return ResultSuccess;
}
}
@@ -1044,9 +1069,11 @@ void KThread::Exit() {
// Register the thread as a work task.
KWorkerTaskManager::AddTask(kernel, KWorkerTaskManager::WorkerType::Exit, this);
}
+
+ UNREACHABLE_MSG("KThread::Exit() would return");
}
-ResultCode KThread::Sleep(s64 timeout) {
+Result KThread::Sleep(s64 timeout) {
ASSERT(!kernel.GlobalSchedulerContext().IsLocked());
ASSERT(this == GetCurrentThreadPointer(kernel));
ASSERT(timeout > 0);
@@ -1079,17 +1106,12 @@ void KThread::IfDummyThreadTryWait() {
return;
}
- // Block until we can grab the lock.
- KScopedSpinLock lk{dummy_wait_lock};
-}
-
-void KThread::IfDummyThreadBeginWait() {
- if (!IsDummyThread()) {
- return;
- }
+ ASSERT(!kernel.IsPhantomModeForSingleCore());
- // 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 +1119,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,12 +1129,9 @@ 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_) {
+void KThread::NotifyAvailable(KSynchronizationObject* signaled_object, Result wait_result_) {
// Lock the scheduler.
KScopedSchedulerLock sl(kernel);
@@ -1122,7 +1141,7 @@ void KThread::NotifyAvailable(KSynchronizationObject* signaled_object, ResultCod
}
}
-void KThread::EndWait(ResultCode wait_result_) {
+void KThread::EndWait(Result wait_result_) {
// Lock the scheduler.
KScopedSchedulerLock sl(kernel);
@@ -1141,7 +1160,7 @@ void KThread::EndWait(ResultCode wait_result_) {
}
}
-void KThread::CancelWait(ResultCode wait_result_, bool cancel_timer_task) {
+void KThread::CancelWait(Result wait_result_, bool cancel_timer_task) {
// Lock the scheduler.
KScopedSchedulerLock sl(kernel);
@@ -1158,10 +1177,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);
}
}
@@ -1170,6 +1190,10 @@ std::shared_ptr<Common::Fiber>& KThread::GetHostContext() {
return host_context;
}
+void SetCurrentThread(KernelCore& kernel, KThread* thread) {
+ kernel.SetCurrentEmuThread(thread);
+}
+
KThread* GetCurrentThreadPointer(KernelCore& kernel) {
return kernel.GetCurrentEmuThread();
}
@@ -1188,16 +1212,13 @@ KScopedDisableDispatch::~KScopedDisableDispatch() {
return;
}
- // Skip the reschedule if single-core, as dispatch tracking is disabled here.
- if (!Settings::values.use_multi_core.GetValue()) {
- return;
- }
-
if (GetCurrentThread(kernel).GetDisableDispatchCount() <= 1) {
- auto scheduler = kernel.CurrentScheduler();
+ auto* scheduler = kernel.CurrentScheduler();
- if (scheduler) {
+ if (scheduler && !kernel.IsPhantomModeForSingleCore()) {
scheduler->RescheduleCurrentCore();
+ } else {
+ KScheduler::RescheduleCurrentHLEThread(kernel);
}
} else {
GetCurrentThread(kernel).EnableDispatch();
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index d058db62c..9ee20208e 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -1,10 +1,12 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
#include <span>
#include <string>
#include <utility>
@@ -14,6 +16,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"
@@ -97,6 +100,13 @@ enum class ThreadWaitReasonForDebugging : u32 {
Suspended, ///< Thread is waiting due to process suspension
};
+enum class StepState : u32 {
+ NotStepping, ///< Thread is not currently stepping
+ StepPending, ///< Thread will step when next scheduled
+ StepPerformed, ///< Thread has stepped, waiting to be scheduled again
+};
+
+void SetCurrentThread(KernelCore& kernel, KThread* thread);
[[nodiscard]] KThread* GetCurrentThreadPointer(KernelCore& kernel);
[[nodiscard]] KThread& GetCurrentThread(KernelCore& kernel);
[[nodiscard]] s32 GetCurrentCoreId(KernelCore& kernel);
@@ -166,7 +176,7 @@ public:
void SetBasePriority(s32 value);
- [[nodiscard]] ResultCode Run();
+ [[nodiscard]] Result Run();
void Exit();
@@ -198,6 +208,8 @@ public:
void Continue();
+ void WaitUntilSuspended();
+
constexpr void SetSyncedIndex(s32 index) {
synced_index = index;
}
@@ -206,11 +218,11 @@ public:
return synced_index;
}
- constexpr void SetWaitResult(ResultCode wait_res) {
+ constexpr void SetWaitResult(Result wait_res) {
wait_result = wait_res;
}
- [[nodiscard]] constexpr ResultCode GetWaitResult() const {
+ [[nodiscard]] constexpr Result GetWaitResult() const {
return wait_result;
}
@@ -255,15 +267,23 @@ 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);
+ [[nodiscard]] StepState GetStepState() const {
+ return step_state;
+ }
+
+ void SetStepState(StepState state) {
+ step_state = state;
+ }
+
[[nodiscard]] s64 GetLastScheduledTick() const {
return last_scheduled_tick;
}
@@ -325,15 +345,15 @@ public:
return physical_affinity_mask;
}
- [[nodiscard]] ResultCode GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask);
+ [[nodiscard]] Result GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask);
- [[nodiscard]] ResultCode GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask);
+ [[nodiscard]] Result GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask);
- [[nodiscard]] ResultCode SetCoreMask(s32 cpu_core_id, u64 v_affinity_mask);
+ [[nodiscard]] Result SetCoreMask(s32 cpu_core_id, u64 v_affinity_mask);
- [[nodiscard]] ResultCode SetActivity(Svc::ThreadActivity activity);
+ [[nodiscard]] Result SetActivity(Svc::ThreadActivity activity);
- [[nodiscard]] ResultCode Sleep(s64 timeout);
+ [[nodiscard]] Result Sleep(s64 timeout);
[[nodiscard]] s64 GetYieldScheduleCount() const {
return schedule_count;
@@ -391,20 +411,22 @@ public:
static void PostDestroy(uintptr_t arg);
- [[nodiscard]] static ResultCode InitializeDummyThread(KThread* thread);
+ [[nodiscard]] static Result InitializeDummyThread(KThread* thread);
+
+ [[nodiscard]] static Result InitializeMainThread(Core::System& system, KThread* thread,
+ s32 virt_core);
- [[nodiscard]] static ResultCode InitializeIdleThread(Core::System& system, KThread* thread,
- s32 virt_core);
+ [[nodiscard]] static Result InitializeIdleThread(Core::System& system, KThread* thread,
+ s32 virt_core);
- [[nodiscard]] static ResultCode InitializeHighPriorityThread(Core::System& system,
- KThread* thread,
- KThreadFunction func,
- uintptr_t arg, s32 virt_core);
+ [[nodiscard]] static Result InitializeHighPriorityThread(Core::System& system, KThread* thread,
+ KThreadFunction func, uintptr_t arg,
+ s32 virt_core);
- [[nodiscard]] static ResultCode InitializeUserThread(Core::System& system, KThread* thread,
- KThreadFunction func, uintptr_t arg,
- VAddr user_stack_top, s32 prio,
- s32 virt_core, KProcess* owner);
+ [[nodiscard]] static Result InitializeUserThread(Core::System& system, KThread* thread,
+ KThreadFunction func, uintptr_t arg,
+ VAddr user_stack_top, s32 prio, s32 virt_core,
+ KProcess* owner);
public:
struct StackParameters {
@@ -461,39 +483,16 @@ public:
return per_core_priority_queue_entry[core];
}
- [[nodiscard]] bool IsKernelThread() const {
- return GetActiveCore() == 3;
- }
-
- [[nodiscard]] bool IsDispatchTrackingDisabled() const {
- return is_single_core || IsKernelThread();
- }
-
[[nodiscard]] s32 GetDisableDispatchCount() const {
- if (IsDispatchTrackingDisabled()) {
- // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
- return 1;
- }
-
return this->GetStackParameters().disable_count;
}
void DisableDispatch() {
- if (IsDispatchTrackingDisabled()) {
- // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
- return;
- }
-
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0);
this->GetStackParameters().disable_count++;
}
void EnableDispatch() {
- if (IsDispatchTrackingDisabled()) {
- // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
- return;
- }
-
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() > 0);
this->GetStackParameters().disable_count--;
}
@@ -590,7 +589,7 @@ public:
void RemoveWaiter(KThread* thread);
- [[nodiscard]] ResultCode GetThreadContext3(std::vector<u8>& out);
+ [[nodiscard]] Result GetThreadContext3(std::vector<u8>& out);
[[nodiscard]] KThread* RemoveWaiterByKey(s32* out_num_waiters, VAddr key);
@@ -616,9 +615,9 @@ public:
}
void BeginWait(KThreadQueue* queue);
- void NotifyAvailable(KSynchronizationObject* signaled_object, ResultCode wait_result_);
- void EndWait(ResultCode wait_result_);
- void CancelWait(ResultCode wait_result_, bool cancel_timer_task);
+ void NotifyAvailable(KSynchronizationObject* signaled_object, Result wait_result_);
+ void EndWait(Result wait_result_);
+ void CancelWait(Result wait_result_, bool cancel_timer_task);
[[nodiscard]] bool HasWaiters() const {
return !waiter_list.empty();
@@ -641,9 +640,16 @@ public:
// blocking as needed.
void IfDummyThreadTryWait();
- void IfDummyThreadBeginWait();
void IfDummyThreadEndWait();
+ [[nodiscard]] uintptr_t GetArgument() const {
+ return argument;
+ }
+
+ [[nodiscard]] VAddr GetUserStackTop() const {
+ return stack_top;
+ }
+
private:
static constexpr size_t PriorityInheritanceCountMax = 10;
union SyncObjectBuffer {
@@ -656,7 +662,7 @@ private:
static_assert(sizeof(SyncObjectBuffer::sync_objects) == sizeof(SyncObjectBuffer::handles));
struct ConditionVariableComparator {
- struct LightCompareType {
+ struct RedBlackKeyType {
u64 cv_key{};
s32 priority{};
@@ -672,8 +678,8 @@ private:
template <typename T>
requires(
std::same_as<T, KThread> ||
- std::same_as<T, LightCompareType>) static constexpr int Compare(const T& lhs,
- const KThread& rhs) {
+ std::same_as<T, RedBlackKeyType>) static constexpr int Compare(const T& lhs,
+ const KThread& rhs) {
const u64 l_key = lhs.GetConditionVariableKey();
const u64 r_key = rhs.GetConditionVariableKey();
@@ -697,14 +703,13 @@ private:
void FinishTermination();
- [[nodiscard]] ResultCode Initialize(KThreadFunction func, uintptr_t arg, VAddr user_stack_top,
- s32 prio, s32 virt_core, KProcess* owner, ThreadType type);
+ [[nodiscard]] Result Initialize(KThreadFunction func, uintptr_t arg, VAddr user_stack_top,
+ s32 prio, s32 virt_core, KProcess* owner, ThreadType type);
- [[nodiscard]] static ResultCode InitializeThread(KThread* thread, KThreadFunction func,
- uintptr_t arg, VAddr user_stack_top, s32 prio,
- s32 core, KProcess* owner, ThreadType type,
- std::function<void(void*)>&& init_func,
- void* init_func_parameter);
+ [[nodiscard]] static Result InitializeThread(KThread* thread, KThreadFunction func,
+ uintptr_t arg, VAddr user_stack_top, s32 prio,
+ s32 core, KProcess* owner, ThreadType type,
+ std::function<void()>&& init_func);
static void RestorePriority(KernelCore& kernel_ctx, KThread* thread);
@@ -741,7 +746,7 @@ private:
u32 suspend_request_flags{};
u32 suspend_allowed_flags{};
s32 synced_index{};
- ResultCode wait_result{ResultSuccess};
+ Result wait_result{ResultSuccess};
s32 base_priority{};
s32 physical_ideal_core_id{};
s32 virtual_ideal_core_id{};
@@ -751,7 +756,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,18 +766,22 @@ 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{};
+ StepState step_state{};
+ std::mutex dummy_wait_lock;
+ std::condition_variable dummy_wait_cv;
// For debugging
std::vector<KSynchronizationObject*> wait_objects_for_debugging;
VAddr mutex_wait_address_for_debugging{};
ThreadWaitReasonForDebugging wait_reason_for_debugging{};
+ uintptr_t argument;
+ VAddr stack_top;
public:
using ConditionVariableThreadTreeType = ConditionVariableThreadTree;
diff --git a/src/core/hle/kernel/k_thread_local_page.cpp b/src/core/hle/kernel/k_thread_local_page.cpp
new file mode 100644
index 000000000..563560114
--- /dev/null
+++ b/src/core/hle/kernel/k_thread_local_page.cpp
@@ -0,0 +1,67 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/scope_exit.h"
+#include "core/core.h"
+
+#include "core/hle/kernel/k_memory_block.h"
+#include "core/hle/kernel/k_page_buffer.h"
+#include "core/hle/kernel/k_page_table.h"
+#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/k_thread_local_page.h"
+#include "core/hle/kernel/kernel.h"
+
+namespace Kernel {
+
+Result KThreadLocalPage::Initialize(KernelCore& kernel, KProcess* process) {
+ // Set that this process owns us.
+ m_owner = process;
+ m_kernel = &kernel;
+
+ // Allocate a new page.
+ KPageBuffer* page_buf = KPageBuffer::Allocate(kernel);
+ R_UNLESS(page_buf != nullptr, ResultOutOfMemory);
+ auto page_buf_guard = SCOPE_GUARD({ KPageBuffer::Free(kernel, page_buf); });
+
+ // Map the address in.
+ const auto phys_addr = kernel.System().DeviceMemory().GetPhysicalAddr(page_buf);
+ R_TRY(m_owner->PageTable().MapPages(std::addressof(m_virt_addr), 1, PageSize, phys_addr,
+ KMemoryState::ThreadLocal,
+ KMemoryPermission::UserReadWrite));
+
+ // We succeeded.
+ page_buf_guard.Cancel();
+
+ return ResultSuccess;
+}
+
+Result KThreadLocalPage::Finalize() {
+ // Get the physical address of the page.
+ const PAddr phys_addr = m_owner->PageTable().GetPhysicalAddr(m_virt_addr);
+ ASSERT(phys_addr);
+
+ // Unmap the page.
+ R_TRY(m_owner->PageTable().UnmapPages(this->GetAddress(), 1, KMemoryState::ThreadLocal));
+
+ // Free the page.
+ KPageBuffer::Free(*m_kernel, KPageBuffer::FromPhysicalAddress(m_kernel->System(), phys_addr));
+
+ return ResultSuccess;
+}
+
+VAddr KThreadLocalPage::Reserve() {
+ for (size_t i = 0; i < m_is_region_free.size(); i++) {
+ if (m_is_region_free[i]) {
+ m_is_region_free[i] = false;
+ return this->GetRegionAddress(i);
+ }
+ }
+
+ return 0;
+}
+
+void KThreadLocalPage::Release(VAddr addr) {
+ m_is_region_free[this->GetRegionIndex(addr)] = true;
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_thread_local_page.h b/src/core/hle/kernel/k_thread_local_page.h
new file mode 100644
index 000000000..0a7f22680
--- /dev/null
+++ b/src/core/hle/kernel/k_thread_local_page.h
@@ -0,0 +1,110 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <algorithm>
+#include <array>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/intrusive_red_black_tree.h"
+#include "core/hle/kernel/memory_types.h"
+#include "core/hle/kernel/slab_helpers.h"
+#include "core/hle/result.h"
+
+namespace Kernel {
+
+class KernelCore;
+class KProcess;
+
+class KThreadLocalPage final : public Common::IntrusiveRedBlackTreeBaseNode<KThreadLocalPage>,
+ public KSlabAllocated<KThreadLocalPage> {
+public:
+ static constexpr size_t RegionsPerPage = PageSize / Svc::ThreadLocalRegionSize;
+ static_assert(RegionsPerPage > 0);
+
+public:
+ constexpr explicit KThreadLocalPage(VAddr addr = {}) : m_virt_addr(addr) {
+ m_is_region_free.fill(true);
+ }
+
+ constexpr VAddr GetAddress() const {
+ return m_virt_addr;
+ }
+
+ Result Initialize(KernelCore& kernel, KProcess* process);
+ Result Finalize();
+
+ VAddr Reserve();
+ void Release(VAddr addr);
+
+ bool IsAllUsed() const {
+ return std::ranges::all_of(m_is_region_free.begin(), m_is_region_free.end(),
+ [](bool is_free) { return !is_free; });
+ }
+
+ bool IsAllFree() const {
+ return std::ranges::all_of(m_is_region_free.begin(), m_is_region_free.end(),
+ [](bool is_free) { return is_free; });
+ }
+
+ bool IsAnyUsed() const {
+ return !this->IsAllFree();
+ }
+
+ bool IsAnyFree() const {
+ return !this->IsAllUsed();
+ }
+
+public:
+ using RedBlackKeyType = VAddr;
+
+ static constexpr RedBlackKeyType GetRedBlackKey(const RedBlackKeyType& v) {
+ return v;
+ }
+ static constexpr RedBlackKeyType GetRedBlackKey(const KThreadLocalPage& v) {
+ return v.GetAddress();
+ }
+
+ template <typename T>
+ requires(std::same_as<T, KThreadLocalPage> ||
+ std::same_as<T, RedBlackKeyType>) static constexpr int Compare(const T& lhs,
+ const KThreadLocalPage&
+ rhs) {
+ const VAddr lval = GetRedBlackKey(lhs);
+ const VAddr rval = GetRedBlackKey(rhs);
+
+ if (lval < rval) {
+ return -1;
+ } else if (lval == rval) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+private:
+ constexpr VAddr GetRegionAddress(size_t i) const {
+ return this->GetAddress() + i * Svc::ThreadLocalRegionSize;
+ }
+
+ constexpr bool Contains(VAddr addr) const {
+ return this->GetAddress() <= addr && addr < this->GetAddress() + PageSize;
+ }
+
+ constexpr size_t GetRegionIndex(VAddr addr) const {
+ ASSERT(Common::IsAligned(addr, Svc::ThreadLocalRegionSize));
+ ASSERT(this->Contains(addr));
+ return (addr - this->GetAddress()) / Svc::ThreadLocalRegionSize;
+ }
+
+private:
+ VAddr m_virt_addr{};
+ KProcess* m_owner{};
+ KernelCore* m_kernel{};
+ std::array<bool, RegionsPerPage> m_is_region_free{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_thread_queue.cpp b/src/core/hle/kernel/k_thread_queue.cpp
index d5248b547..9f4e081ba 100644
--- a/src/core/hle/kernel/k_thread_queue.cpp
+++ b/src/core/hle/kernel/k_thread_queue.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_thread_queue.h"
#include "core/hle/kernel/kernel.h"
@@ -10,9 +9,9 @@ namespace Kernel {
void KThreadQueue::NotifyAvailable([[maybe_unused]] KThread* waiting_thread,
[[maybe_unused]] KSynchronizationObject* signaled_object,
- [[maybe_unused]] ResultCode wait_result) {}
+ [[maybe_unused]] Result wait_result) {}
-void KThreadQueue::EndWait(KThread* waiting_thread, ResultCode wait_result) {
+void KThreadQueue::EndWait(KThread* waiting_thread, Result wait_result) {
// Set the thread's wait result.
waiting_thread->SetWaitResult(wait_result);
@@ -26,8 +25,7 @@ void KThreadQueue::EndWait(KThread* waiting_thread, ResultCode wait_result) {
kernel.TimeManager().UnscheduleTimeEvent(waiting_thread);
}
-void KThreadQueue::CancelWait(KThread* waiting_thread, ResultCode wait_result,
- bool cancel_timer_task) {
+void KThreadQueue::CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) {
// Set the thread's wait result.
waiting_thread->SetWaitResult(wait_result);
@@ -44,6 +42,6 @@ void KThreadQueue::CancelWait(KThread* waiting_thread, ResultCode wait_result,
}
void KThreadQueueWithoutEndWait::EndWait([[maybe_unused]] KThread* waiting_thread,
- [[maybe_unused]] ResultCode wait_result) {}
+ [[maybe_unused]] Result wait_result) {}
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_thread_queue.h b/src/core/hle/kernel/k_thread_queue.h
index ccb718e49..8d76ece81 100644
--- a/src/core/hle/kernel/k_thread_queue.h
+++ b/src/core/hle/kernel/k_thread_queue.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -15,10 +14,9 @@ public:
virtual ~KThreadQueue() = default;
virtual void NotifyAvailable(KThread* waiting_thread, KSynchronizationObject* signaled_object,
- ResultCode wait_result);
- virtual void EndWait(KThread* waiting_thread, ResultCode wait_result);
- virtual void CancelWait(KThread* waiting_thread, ResultCode wait_result,
- bool cancel_timer_task);
+ Result wait_result);
+ virtual void EndWait(KThread* waiting_thread, Result wait_result);
+ virtual void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task);
private:
KernelCore& kernel;
@@ -29,7 +27,7 @@ class KThreadQueueWithoutEndWait : public KThreadQueue {
public:
explicit KThreadQueueWithoutEndWait(KernelCore& kernel_) : KThreadQueue(kernel_) {}
- void EndWait(KThread* waiting_thread, ResultCode wait_result) override final;
+ void EndWait(KThread* waiting_thread, Result wait_result) override final;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_trace.h b/src/core/hle/kernel/k_trace.h
index d3fed1888..d61af4830 100644
--- a/src/core/hle/kernel/k_trace.h
+++ b/src/core/hle/kernel/k_trace.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_transfer_memory.cpp b/src/core/hle/kernel/k_transfer_memory.cpp
index 1732925c9..b0320eb73 100644
--- a/src/core/hle/kernel/k_transfer_memory.cpp
+++ b/src/core/hle/kernel/k_transfer_memory.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"
@@ -14,8 +13,8 @@ KTransferMemory::KTransferMemory(KernelCore& kernel_)
KTransferMemory::~KTransferMemory() = default;
-ResultCode KTransferMemory::Initialize(VAddr address_, std::size_t size_,
- Svc::MemoryPermission owner_perm_) {
+Result KTransferMemory::Initialize(VAddr address_, std::size_t size_,
+ Svc::MemoryPermission owner_perm_) {
// Set members.
owner = kernel.CurrentProcess();
diff --git a/src/core/hle/kernel/k_transfer_memory.h b/src/core/hle/kernel/k_transfer_memory.h
index cb7521823..85d508ee7 100644
--- a/src/core/hle/kernel/k_transfer_memory.h
+++ b/src/core/hle/kernel/k_transfer_memory.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,7 +7,7 @@
#include "core/hle/kernel/svc_types.h"
#include "core/hle/result.h"
-union ResultCode;
+union Result;
namespace Core::Memory {
class Memory;
@@ -27,7 +26,7 @@ public:
explicit KTransferMemory(KernelCore& kernel_);
~KTransferMemory() override;
- ResultCode Initialize(VAddr address_, std::size_t size_, Svc::MemoryPermission owner_perm_);
+ Result Initialize(VAddr address_, std::size_t size_, Svc::MemoryPermission owner_perm_);
void Finalize() override;
diff --git a/src/core/hle/kernel/k_worker_task.h b/src/core/hle/kernel/k_worker_task.h
index b7794c6a8..ef591d831 100644
--- a/src/core/hle/kernel/k_worker_task.h
+++ b/src/core/hle/kernel/k_worker_task.h
@@ -1,6 +1,5 @@
-// Copyright 2022 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_worker_task_manager.cpp b/src/core/hle/kernel/k_worker_task_manager.cpp
index 785e08111..04042bf8f 100644
--- a/src/core/hle/kernel/k_worker_task_manager.cpp
+++ b/src/core/hle/kernel/k_worker_task_manager.cpp
@@ -1,6 +1,5 @@
-// Copyright 2022 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "core/hle/kernel/k_process.h"
@@ -24,7 +23,7 @@ void KWorkerTask::DoWorkerTask() {
}
}
-KWorkerTaskManager::KWorkerTaskManager() : m_waiting_thread(1, "yuzu:KWorkerTaskManager") {}
+KWorkerTaskManager::KWorkerTaskManager() : m_waiting_thread(1, "KWorkerTaskManager") {}
void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task) {
ASSERT(type <= WorkerType::Count);
diff --git a/src/core/hle/kernel/k_worker_task_manager.h b/src/core/hle/kernel/k_worker_task_manager.h
index 43d1bfcec..f6618883e 100644
--- a/src/core/hle/kernel/k_worker_task_manager.h
+++ b/src/core/hle/kernel/k_worker_task_manager.h
@@ -1,6 +1,5 @@
-// Copyright 2022 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/k_writable_event.cpp b/src/core/hle/kernel/k_writable_event.cpp
index bdb1db6d5..ff88c5acd 100644
--- a/src/core/hle/kernel/k_writable_event.cpp
+++ b/src/core/hle/kernel/k_writable_event.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
@@ -19,11 +18,11 @@ void KWritableEvent::Initialize(KEvent* parent_event_, std::string&& name_) {
parent->GetReadableEvent().Open();
}
-ResultCode KWritableEvent::Signal() {
+Result KWritableEvent::Signal() {
return parent->GetReadableEvent().Signal();
}
-ResultCode KWritableEvent::Clear() {
+Result KWritableEvent::Clear() {
return parent->GetReadableEvent().Clear();
}
diff --git a/src/core/hle/kernel/k_writable_event.h b/src/core/hle/kernel/k_writable_event.h
index 858d982c4..3fd0c7d0a 100644
--- a/src/core/hle/kernel/k_writable_event.h
+++ b/src/core/hle/kernel/k_writable_event.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -26,8 +25,8 @@ public:
static void PostDestroy([[maybe_unused]] uintptr_t arg) {}
void Initialize(KEvent* parent_, std::string&& name_);
- ResultCode Signal();
- ResultCode Clear();
+ Result Signal();
+ Result Clear();
KEvent* GetParent() const {
return parent;
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 49c0714ed..9251f29ad 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <atomic>
@@ -18,13 +17,10 @@
#include "common/thread.h"
#include "common/thread_worker.h"
#include "core/arm/arm_interface.h"
-#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
#include "core/core_timing.h"
-#include "core/core_timing_util.h"
#include "core/cpu_manager.h"
-#include "core/device_memory.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/init/init_slab_setup.h"
#include "core/hle/kernel/k_client_port.h"
@@ -35,7 +31,6 @@
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_shared_memory.h"
-#include "core/hle/kernel/k_slab_heap.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_worker_task_manager.h"
#include "core/hle/kernel/kernel.h"
@@ -52,42 +47,41 @@ namespace Kernel {
struct KernelCore::Impl {
explicit Impl(Core::System& system_, KernelCore& kernel_)
- : time_manager{system_}, object_list_container{kernel_},
- service_threads_manager{1, "yuzu:ServiceThreadsManager"}, system{system_} {}
+ : time_manager{system_},
+ service_threads_manager{1, "ServiceThreadsManager"}, system{system_} {}
void SetMulticore(bool is_multi) {
is_multicore = is_multi;
}
void Initialize(KernelCore& kernel) {
+ global_object_list_container = std::make_unique<KAutoObjectWithListContainer>(kernel);
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;
- InitializePhysicalCores();
-
// Derive the initial memory layout from the emulated board
Init::InitializeSlabResourceCounts(kernel);
- KMemoryLayout memory_layout;
- DeriveInitialMemoryLayout(memory_layout);
- Init::InitializeSlabHeaps(system, memory_layout);
+ DeriveInitialMemoryLayout();
+ Init::InitializeSlabHeaps(system, *memory_layout);
// Initialize kernel memory and resources.
- InitializeSystemResourceLimit(kernel, system.CoreTiming(), memory_layout);
- InitializeMemoryLayout(memory_layout);
- InitializePageSlab();
- InitializeSchedulers();
- InitializeSuspendThreads();
+ InitializeSystemResourceLimit(kernel, system.CoreTiming());
+ InitializeMemoryLayout();
+ Init::InitializeKPageBufferSlabHeap(system);
+ InitializeShutdownThreads();
InitializePreemption(kernel);
+ InitializePhysicalCores();
RegisterHostThread();
}
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);
}
}
@@ -98,39 +92,16 @@ struct KernelCore::Impl {
process_list.clear();
- // Close all open server ports.
- std::unordered_set<KServerPort*> server_ports_;
- {
- std::lock_guard lk(server_ports_lock);
- server_ports_ = server_ports;
- server_ports.clear();
- }
- for (auto* server_port : server_ports_) {
- server_port->Close();
- }
- // Close all open server sessions.
- std::unordered_set<KServerSession*> server_sessions_;
- {
- std::lock_guard lk(server_sessions_lock);
- server_sessions_ = server_sessions;
- server_sessions.clear();
- }
- for (auto* server_session : server_sessions_) {
- server_session->Close();
- }
-
- // Ensure that the object list container is finalized and properly shutdown.
- object_list_container.Finalize();
-
- // Ensures all service threads gracefully shutdown.
- ClearServiceThreads();
+ CloseServices();
next_object_id = 0;
next_kernel_process_id = KProcess::InitialKIPIDMin;
next_user_process_id = KProcess::ProcessIDMin;
next_thread_id = 1;
- cores.clear();
+ for (auto& core : cores) {
+ core = nullptr;
+ }
global_handle_table->Finalize();
global_handle_table.reset();
@@ -155,15 +126,15 @@ 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++) {
- if (suspend_threads[core_id]) {
- suspend_threads[core_id]->Close();
- suspend_threads[core_id] = nullptr;
+ if (shutdown_threads[core_id]) {
+ shutdown_threads[core_id]->Close();
+ shutdown_threads[core_id] = nullptr;
}
- schedulers[core_id]->Finalize();
schedulers[core_id].reset();
}
@@ -172,7 +143,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();
@@ -183,48 +154,76 @@ 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_WARNING(Kernel, "{} kernel objects were dangling on shutdown!",
- registered_objects.size());
+ LOG_DEBUG(Kernel, "{} kernel objects were dangling on shutdown!",
+ registered_objects.size());
registered_objects.clear();
}
}
+
+ // Ensure that the object list container is finalized and properly shutdown.
+ global_object_list_container->Finalize();
+ global_object_list_container.reset();
+ }
+
+ void CloseServices() {
+ // Close all open server sessions and ports.
+ std::unordered_set<KAutoObject*> server_objects_;
+ {
+ std::scoped_lock lk(server_objects_lock);
+ server_objects_ = server_objects;
+ server_objects.clear();
+ }
+ for (auto* server_object : server_objects_) {
+ server_object->Close();
+ }
+
+ // Ensures all service threads gracefully shutdown.
+ ClearServiceThreads();
}
void InitializePhysicalCores() {
exclusive_monitor =
Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES);
for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
- schedulers[i] = std::make_unique<Kernel::KScheduler>(system, i);
- cores.emplace_back(i, system, *schedulers[i], interrupts);
- }
- }
+ const s32 core{static_cast<s32>(i)};
- void InitializeSchedulers() {
- for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
- cores[i].Scheduler().Initialize();
+ schedulers[i] = std::make_unique<Kernel::KScheduler>(system.Kernel());
+ cores[i] = std::make_unique<Kernel::PhysicalCore>(i, system, *schedulers[i]);
+
+ auto* main_thread{Kernel::KThread::Create(system.Kernel())};
+ main_thread->SetName(fmt::format("MainThread:{}", core));
+ main_thread->SetCurrentCore(core);
+ ASSERT(Kernel::KThread::InitializeMainThread(system, main_thread, core).IsSuccess());
+
+ auto* idle_thread{Kernel::KThread::Create(system.Kernel())};
+ idle_thread->SetCurrentCore(core);
+ ASSERT(Kernel::KThread::InitializeIdleThread(system, idle_thread, core).IsSuccess());
+
+ schedulers[i]->Initialize(main_thread, idle_thread, core);
}
}
// Creates the default system resource limit
void InitializeSystemResourceLimit(KernelCore& kernel,
- const Core::Timing::CoreTiming& core_timing,
- const KMemoryLayout& memory_layout) {
+ const Core::Timing::CoreTiming& core_timing) {
system_resource_limit = KResourceLimit::Create(system.Kernel());
system_resource_limit->Initialize(&core_timing);
- const auto [total_size, kernel_size] = memory_layout.GetTotalAndKernelMemorySizes();
+ const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()};
+ const auto total_size{sizes.first};
+ const auto kernel_size{sizes.second};
// If setting the default system values fails, then something seriously wrong has occurred.
ASSERT(system_resource_limit->SetLimitValue(LimitableResource::PhysicalMemory, total_size)
@@ -240,37 +239,31 @@ struct KernelCore::Impl {
constexpr u64 secure_applet_memory_size{4_MiB};
ASSERT(system_resource_limit->Reserve(LimitableResource::PhysicalMemory,
secure_applet_memory_size));
-
- // This memory seems to be reserved on hardware, but is not reserved/used by yuzu.
- // Likely Horizon OS reserved memory
- // TODO(ameerj): Derive the memory rather than hardcode it.
- constexpr u64 unknown_reserved_memory{0x2f896000};
- ASSERT(system_resource_limit->Reserve(LimitableResource::PhysicalMemory,
- unknown_reserved_memory));
}
void InitializePreemption(KernelCore& kernel) {
preemption_event = Core::Timing::CreateEvent(
- "PreemptionCallback", [this, &kernel](std::uintptr_t, std::chrono::nanoseconds) {
+ "PreemptionCallback",
+ [this, &kernel](std::uintptr_t, s64 time,
+ std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
{
KScopedSchedulerLock lock(kernel);
global_scheduler_context->PreemptThreads();
}
- const auto time_interval = std::chrono::nanoseconds{std::chrono::milliseconds(10)};
- system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
+ return std::nullopt;
});
const auto time_interval = std::chrono::nanoseconds{std::chrono::milliseconds(10)};
- system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
+ system.CoreTiming().ScheduleLoopingEvent(time_interval, time_interval, preemption_event);
}
- void InitializeSuspendThreads() {
+ void InitializeShutdownThreads() {
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
- suspend_threads[core_id] = KThread::Create(system.Kernel());
- ASSERT(KThread::InitializeHighPriorityThread(system, suspend_threads[core_id], {}, {},
+ shutdown_threads[core_id] = KThread::Create(system.Kernel());
+ ASSERT(KThread::InitializeHighPriorityThread(system, shutdown_threads[core_id], {}, {},
core_id)
.IsSuccess());
- suspend_threads[core_id]->SetName(fmt::format("SuspendThread:{}", core_id));
+ shutdown_threads[core_id]->SetName(fmt::format("SuspendThread:{}", core_id));
}
}
@@ -300,15 +293,16 @@ struct KernelCore::Impl {
// Gets the dummy KThread for the caller, allocating a new one if this is the first time
KThread* GetHostDummyThread() {
- auto make_thread = [this]() {
- KThread* thread = KThread::Create(system.Kernel());
+ auto initialize = [this](KThread* thread) {
ASSERT(KThread::InitializeDummyThread(thread).IsSuccess());
thread->SetName(fmt::format("DummyThread:{}", GetHostThreadId()));
return thread;
};
- thread_local KThread* saved_thread = make_thread();
- return saved_thread;
+ thread_local auto raw_thread = KThread(system.Kernel());
+ thread_local auto thread = initialize(&raw_thread);
+
+ return thread;
}
/// Registers a CPU core thread by allocating a host thread ID for it
@@ -347,6 +341,8 @@ struct KernelCore::Impl {
return is_shutting_down.load(std::memory_order_relaxed);
}
+ static inline thread_local KThread* current_thread{nullptr};
+
KThread* GetCurrentEmuThread() {
// If we are shutting down the kernel, none of this is relevant anymore.
if (IsShuttingDown()) {
@@ -357,19 +353,26 @@ struct KernelCore::Impl {
if (thread_id >= Core::Hardware::NUM_CPU_CORES) {
return GetHostDummyThread();
}
- return schedulers[thread_id]->GetCurrentThread();
+
+ return current_thread;
+ }
+
+ void SetCurrentEmuThread(KThread* thread) {
+ current_thread = thread;
}
- void DeriveInitialMemoryLayout(KMemoryLayout& memory_layout) {
+ void DeriveInitialMemoryLayout() {
+ memory_layout = std::make_unique<KMemoryLayout>();
+
// Insert the root region for the virtual memory tree, from which all other regions will
// derive.
- memory_layout.GetVirtualMemoryRegionTree().InsertDirectly(
+ memory_layout->GetVirtualMemoryRegionTree().InsertDirectly(
KernelVirtualAddressSpaceBase,
KernelVirtualAddressSpaceBase + KernelVirtualAddressSpaceSize - 1);
// Insert the root region for the physical memory tree, from which all other regions will
// derive.
- memory_layout.GetPhysicalMemoryRegionTree().InsertDirectly(
+ memory_layout->GetPhysicalMemoryRegionTree().InsertDirectly(
KernelPhysicalAddressSpaceBase,
KernelPhysicalAddressSpaceBase + KernelPhysicalAddressSpaceSize - 1);
@@ -386,7 +389,7 @@ struct KernelCore::Impl {
if (!(kernel_region_start + KernelRegionSize - 1 <= KernelVirtualAddressSpaceLast)) {
kernel_region_size = KernelVirtualAddressSpaceEnd - kernel_region_start;
}
- ASSERT(memory_layout.GetVirtualMemoryRegionTree().Insert(
+ ASSERT(memory_layout->GetVirtualMemoryRegionTree().Insert(
kernel_region_start, kernel_region_size, KMemoryRegionType_Kernel));
// Setup the code region.
@@ -395,11 +398,11 @@ struct KernelCore::Impl {
Common::AlignDown(code_start_virt_addr, CodeRegionAlign);
constexpr VAddr code_region_end = Common::AlignUp(code_end_virt_addr, CodeRegionAlign);
constexpr size_t code_region_size = code_region_end - code_region_start;
- ASSERT(memory_layout.GetVirtualMemoryRegionTree().Insert(
+ ASSERT(memory_layout->GetVirtualMemoryRegionTree().Insert(
code_region_start, code_region_size, KMemoryRegionType_KernelCode));
// Setup board-specific device physical regions.
- Init::SetupDevicePhysicalMemoryRegions(memory_layout);
+ Init::SetupDevicePhysicalMemoryRegions(*memory_layout);
// Determine the amount of space needed for the misc region.
size_t misc_region_needed_size;
@@ -408,7 +411,7 @@ struct KernelCore::Impl {
misc_region_needed_size = Core::Hardware::NUM_CPU_CORES * (3 * (PageSize + PageSize));
// Account for each auto-map device.
- for (const auto& region : memory_layout.GetPhysicalMemoryRegionTree()) {
+ for (const auto& region : memory_layout->GetPhysicalMemoryRegionTree()) {
if (region.HasTypeAttribute(KMemoryRegionAttr_ShouldKernelMap)) {
// Check that the region is valid.
ASSERT(region.GetEndAddress() != 0);
@@ -433,22 +436,22 @@ struct KernelCore::Impl {
// Setup the misc region.
const VAddr misc_region_start =
- memory_layout.GetVirtualMemoryRegionTree().GetRandomAlignedRegion(
+ memory_layout->GetVirtualMemoryRegionTree().GetRandomAlignedRegion(
misc_region_size, MiscRegionAlign, KMemoryRegionType_Kernel);
- ASSERT(memory_layout.GetVirtualMemoryRegionTree().Insert(
+ ASSERT(memory_layout->GetVirtualMemoryRegionTree().Insert(
misc_region_start, misc_region_size, KMemoryRegionType_KernelMisc));
// Setup the stack region.
constexpr size_t StackRegionSize = 14_MiB;
constexpr size_t StackRegionAlign = KernelAslrAlignment;
const VAddr stack_region_start =
- memory_layout.GetVirtualMemoryRegionTree().GetRandomAlignedRegion(
+ memory_layout->GetVirtualMemoryRegionTree().GetRandomAlignedRegion(
StackRegionSize, StackRegionAlign, KMemoryRegionType_Kernel);
- ASSERT(memory_layout.GetVirtualMemoryRegionTree().Insert(
+ ASSERT(memory_layout->GetVirtualMemoryRegionTree().Insert(
stack_region_start, StackRegionSize, KMemoryRegionType_KernelStack));
// Determine the size of the resource region.
- const size_t resource_region_size = memory_layout.GetResourceRegionSizeForInit();
+ const size_t resource_region_size = memory_layout->GetResourceRegionSizeForInit();
// Determine the size of the slab region.
const size_t slab_region_size =
@@ -465,23 +468,23 @@ struct KernelCore::Impl {
Common::AlignUp(code_end_phys_addr + slab_region_size, SlabRegionAlign) -
Common::AlignDown(code_end_phys_addr, SlabRegionAlign);
const VAddr slab_region_start =
- memory_layout.GetVirtualMemoryRegionTree().GetRandomAlignedRegion(
+ memory_layout->GetVirtualMemoryRegionTree().GetRandomAlignedRegion(
slab_region_needed_size, SlabRegionAlign, KMemoryRegionType_Kernel) +
(code_end_phys_addr % SlabRegionAlign);
- ASSERT(memory_layout.GetVirtualMemoryRegionTree().Insert(
+ ASSERT(memory_layout->GetVirtualMemoryRegionTree().Insert(
slab_region_start, slab_region_size, KMemoryRegionType_KernelSlab));
// Setup the temp region.
constexpr size_t TempRegionSize = 128_MiB;
constexpr size_t TempRegionAlign = KernelAslrAlignment;
const VAddr temp_region_start =
- memory_layout.GetVirtualMemoryRegionTree().GetRandomAlignedRegion(
+ memory_layout->GetVirtualMemoryRegionTree().GetRandomAlignedRegion(
TempRegionSize, TempRegionAlign, KMemoryRegionType_Kernel);
- ASSERT(memory_layout.GetVirtualMemoryRegionTree().Insert(temp_region_start, TempRegionSize,
- KMemoryRegionType_KernelTemp));
+ ASSERT(memory_layout->GetVirtualMemoryRegionTree().Insert(temp_region_start, TempRegionSize,
+ KMemoryRegionType_KernelTemp));
// Automatically map in devices that have auto-map attributes.
- for (auto& region : memory_layout.GetPhysicalMemoryRegionTree()) {
+ for (auto& region : memory_layout->GetPhysicalMemoryRegionTree()) {
// We only care about kernel regions.
if (!region.IsDerivedFrom(KMemoryRegionType_Kernel)) {
continue;
@@ -508,21 +511,21 @@ struct KernelCore::Impl {
const size_t map_size =
Common::AlignUp(region.GetEndAddress(), PageSize) - map_phys_addr;
const VAddr map_virt_addr =
- memory_layout.GetVirtualMemoryRegionTree().GetRandomAlignedRegionWithGuard(
+ memory_layout->GetVirtualMemoryRegionTree().GetRandomAlignedRegionWithGuard(
map_size, PageSize, KMemoryRegionType_KernelMisc, PageSize);
- ASSERT(memory_layout.GetVirtualMemoryRegionTree().Insert(
+ ASSERT(memory_layout->GetVirtualMemoryRegionTree().Insert(
map_virt_addr, map_size, KMemoryRegionType_KernelMiscMappedDevice));
region.SetPairAddress(map_virt_addr + region.GetAddress() - map_phys_addr);
}
- Init::SetupDramPhysicalMemoryRegions(memory_layout);
+ Init::SetupDramPhysicalMemoryRegions(*memory_layout);
// Insert a physical region for the kernel code region.
- ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert(
+ ASSERT(memory_layout->GetPhysicalMemoryRegionTree().Insert(
code_start_phys_addr, code_region_size, KMemoryRegionType_DramKernelCode));
// Insert a physical region for the kernel slab region.
- ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert(
+ ASSERT(memory_layout->GetPhysicalMemoryRegionTree().Insert(
slab_start_phys_addr, slab_region_size, KMemoryRegionType_DramKernelSlab));
// Determine size available for kernel page table heaps, requiring > 8 MB.
@@ -531,12 +534,12 @@ struct KernelCore::Impl {
ASSERT(page_table_heap_size / 4_MiB > 2);
// Insert a physical region for the kernel page table heap region
- ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert(
+ ASSERT(memory_layout->GetPhysicalMemoryRegionTree().Insert(
slab_end_phys_addr, page_table_heap_size, KMemoryRegionType_DramKernelPtHeap));
// All DRAM regions that we haven't tagged by this point will be mapped under the linear
// mapping. Tag them.
- for (auto& region : memory_layout.GetPhysicalMemoryRegionTree()) {
+ for (auto& region : memory_layout->GetPhysicalMemoryRegionTree()) {
if (region.GetType() == KMemoryRegionType_Dram) {
// Check that the region is valid.
ASSERT(region.GetEndAddress() != 0);
@@ -548,7 +551,7 @@ struct KernelCore::Impl {
// Get the linear region extents.
const auto linear_extents =
- memory_layout.GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(
+ memory_layout->GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(
KMemoryRegionAttr_LinearMapped);
ASSERT(linear_extents.GetEndAddress() != 0);
@@ -560,7 +563,7 @@ struct KernelCore::Impl {
Common::AlignUp(linear_extents.GetEndAddress(), LinearRegionAlign) -
aligned_linear_phys_start;
const VAddr linear_region_start =
- memory_layout.GetVirtualMemoryRegionTree().GetRandomAlignedRegionWithGuard(
+ memory_layout->GetVirtualMemoryRegionTree().GetRandomAlignedRegionWithGuard(
linear_region_size, LinearRegionAlign, KMemoryRegionType_None, LinearRegionAlign);
const u64 linear_region_phys_to_virt_diff = linear_region_start - aligned_linear_phys_start;
@@ -569,7 +572,7 @@ struct KernelCore::Impl {
{
PAddr cur_phys_addr = 0;
u64 cur_size = 0;
- for (auto& region : memory_layout.GetPhysicalMemoryRegionTree()) {
+ for (auto& region : memory_layout->GetPhysicalMemoryRegionTree()) {
if (!region.HasTypeAttribute(KMemoryRegionAttr_LinearMapped)) {
continue;
}
@@ -588,55 +591,49 @@ struct KernelCore::Impl {
const VAddr region_virt_addr =
region.GetAddress() + linear_region_phys_to_virt_diff;
- ASSERT(memory_layout.GetVirtualMemoryRegionTree().Insert(
+ ASSERT(memory_layout->GetVirtualMemoryRegionTree().Insert(
region_virt_addr, region.GetSize(),
GetTypeForVirtualLinearMapping(region.GetType())));
region.SetPairAddress(region_virt_addr);
KMemoryRegion* virt_region =
- memory_layout.GetVirtualMemoryRegionTree().FindModifiable(region_virt_addr);
+ memory_layout->GetVirtualMemoryRegionTree().FindModifiable(region_virt_addr);
ASSERT(virt_region != nullptr);
virt_region->SetPairAddress(region.GetAddress());
}
}
// Insert regions for the initial page table region.
- ASSERT(memory_layout.GetPhysicalMemoryRegionTree().Insert(
+ ASSERT(memory_layout->GetPhysicalMemoryRegionTree().Insert(
resource_end_phys_addr, KernelPageTableHeapSize, KMemoryRegionType_DramKernelInitPt));
- ASSERT(memory_layout.GetVirtualMemoryRegionTree().Insert(
+ ASSERT(memory_layout->GetVirtualMemoryRegionTree().Insert(
resource_end_phys_addr + linear_region_phys_to_virt_diff, KernelPageTableHeapSize,
KMemoryRegionType_VirtualDramKernelInitPt));
// All linear-mapped DRAM regions that we haven't tagged by this point will be allocated to
// some pool partition. Tag them.
- for (auto& region : memory_layout.GetPhysicalMemoryRegionTree()) {
+ for (auto& region : memory_layout->GetPhysicalMemoryRegionTree()) {
if (region.GetType() == (KMemoryRegionType_Dram | KMemoryRegionAttr_LinearMapped)) {
region.SetType(KMemoryRegionType_DramPoolPartition);
}
}
// Setup all other memory regions needed to arrange the pool partitions.
- Init::SetupPoolPartitionMemoryRegions(memory_layout);
+ Init::SetupPoolPartitionMemoryRegions(*memory_layout);
// Cache all linear regions in their own trees for faster access, later.
- memory_layout.InitializeLinearMemoryRegionTrees(aligned_linear_phys_start,
- linear_region_start);
+ memory_layout->InitializeLinearMemoryRegionTrees(aligned_linear_phys_start,
+ linear_region_start);
}
- void InitializeMemoryLayout(const KMemoryLayout& memory_layout) {
- const auto system_pool = memory_layout.GetKernelSystemPoolRegionPhysicalExtents();
- const auto applet_pool = memory_layout.GetKernelAppletPoolRegionPhysicalExtents();
- const auto application_pool = memory_layout.GetKernelApplicationPoolRegionPhysicalExtents();
+ void InitializeMemoryLayout() {
+ const auto system_pool = memory_layout->GetKernelSystemPoolRegionPhysicalExtents();
- // Initialize memory managers
+ // Initialize the memory manager.
memory_manager = std::make_unique<KMemoryManager>(system);
- memory_manager->InitializeManager(KMemoryManager::Pool::Application,
- application_pool.GetAddress(),
- application_pool.GetEndAddress());
- memory_manager->InitializeManager(KMemoryManager::Pool::Applet, applet_pool.GetAddress(),
- applet_pool.GetEndAddress());
- memory_manager->InitializeManager(KMemoryManager::Pool::System, system_pool.GetAddress(),
- system_pool.GetEndAddress());
+ const auto& management_region = memory_layout->GetPoolManagementRegion();
+ ASSERT(management_region.GetEndAddress() != 0);
+ memory_manager->Initialize(management_region.GetAddress(), management_region.GetSize());
// Setup memory regions for emulated processes
// TODO(bunnei): These should not be hardcoded regions initialized within the kernel
@@ -644,16 +641,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},
@@ -671,22 +672,10 @@ struct KernelCore::Impl {
{time_phys_addr, time_size / PageSize},
Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
time_phys_addr, time_size, "Time:SharedMemory");
- }
-
- void InitializePageSlab() {
- // Allocate slab heaps
- user_slab_heap_pages =
- std::make_unique<KSlabHeap<Page>>(KSlabHeap<Page>::AllocationType::Guest);
-
- // TODO(ameerj): This should be derived, not hardcoded within the kernel
- constexpr u64 user_slab_heap_size{0x3de000};
- // Reserve slab heaps
- ASSERT(
- system_resource_limit->Reserve(LimitableResource::PhysicalMemory, user_slab_heap_size));
- // Initialize slab heap
- user_slab_heap_pages->Initialize(
- system.DeviceMemory().GetPointer(Core::DramMemoryMap::SlabHeapBase),
- user_slab_heap_size);
+ 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) {
@@ -697,13 +686,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);
@@ -716,6 +712,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); });
}
@@ -725,8 +727,7 @@ struct KernelCore::Impl {
service_threads_manager.QueueWork([this]() { service_threads.clear(); });
}
- std::mutex server_ports_lock;
- std::mutex server_sessions_lock;
+ std::mutex server_objects_lock;
std::mutex registered_objects_lock;
std::mutex registered_in_use_objects_lock;
@@ -737,7 +738,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;
@@ -750,39 +751,41 @@ struct KernelCore::Impl {
// stores all the objects in place.
std::unique_ptr<KHandleTable> global_handle_table;
- KAutoObjectWithListContainer object_list_container;
+ std::unique_ptr<KAutoObjectWithListContainer> global_object_list_container;
/// Map of named ports managed by the kernel, which can be retrieved using
/// the ConnectToPort SVC.
std::unordered_map<std::string, ServiceInterfaceFactory> service_interface_factory;
NamedPortTable named_ports;
- std::unordered_set<KServerPort*> server_ports;
- std::unordered_set<KServerSession*> server_sessions;
+ std::unordered_set<KAutoObject*> server_objects;
std::unordered_set<KAutoObject*> registered_objects;
std::unordered_set<KAutoObject*> registered_in_use_objects;
std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
- std::vector<Kernel::PhysicalCore> cores;
+ std::array<std::unique_ptr<Kernel::PhysicalCore>, Core::Hardware::NUM_CPU_CORES> cores;
// Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others
std::atomic<u32> next_host_thread_id{Core::Hardware::NUM_CPU_CORES};
// Kernel memory management
std::unique_ptr<KMemoryManager> memory_manager;
- std::unique_ptr<KSlabHeap<Page>> user_slab_heap_pages;
// Shared memory for services
Kernel::KSharedMemory* hid_shared_mem{};
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;
- std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
+ std::array<KThread*, Core::Hardware::NUM_CPU_CORES> shutdown_threads;
std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
bool is_multicore{};
@@ -818,6 +821,10 @@ void KernelCore::Shutdown() {
impl->Shutdown();
}
+void KernelCore::CloseServices() {
+ impl->CloseServices();
+}
+
const KResourceLimit* KernelCore::GetSystemResourceLimit() const {
return impl->system_resource_limit;
}
@@ -867,11 +874,11 @@ const Kernel::KScheduler& KernelCore::Scheduler(std::size_t id) const {
}
Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) {
- return impl->cores[id];
+ return *impl->cores[id];
}
const Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) const {
- return impl->cores[id];
+ return *impl->cores[id];
}
size_t KernelCore::CurrentPhysicalCoreIndex() const {
@@ -883,11 +890,11 @@ size_t KernelCore::CurrentPhysicalCoreIndex() const {
}
Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() {
- return impl->cores[CurrentPhysicalCoreIndex()];
+ return *impl->cores[CurrentPhysicalCoreIndex()];
}
const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const {
- return impl->cores[CurrentPhysicalCoreIndex()];
+ return *impl->cores[CurrentPhysicalCoreIndex()];
}
Kernel::KScheduler* KernelCore::CurrentScheduler() {
@@ -899,15 +906,6 @@ Kernel::KScheduler* KernelCore::CurrentScheduler() {
return impl->schedulers[core_id].get();
}
-std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& KernelCore::Interrupts() {
- return impl->interrupts;
-}
-
-const std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& KernelCore::Interrupts()
- const {
- return impl->interrupts;
-}
-
Kernel::TimeManager& KernelCore::TimeManager() {
return impl->time_manager;
}
@@ -925,25 +923,25 @@ const Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() const {
}
KAutoObjectWithListContainer& KernelCore::ObjectListContainer() {
- return impl->object_list_container;
+ return *impl->global_object_list_container;
}
const KAutoObjectWithListContainer& KernelCore::ObjectListContainer() const {
- return impl->object_list_container;
+ return *impl->global_object_list_container;
}
void KernelCore::InvalidateAllInstructionCaches() {
for (auto& physical_core : impl->cores) {
- physical_core.ArmInterface().ClearInstructionCache();
+ physical_core->ArmInterface().ClearInstructionCache();
}
}
void KernelCore::InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size) {
for (auto& physical_core : impl->cores) {
- if (!physical_core.IsInitialized()) {
+ if (!physical_core->IsInitialized()) {
continue;
}
- physical_core.ArmInterface().InvalidateCacheRange(addr, size);
+ physical_core->ArmInterface().InvalidateCacheRange(addr, size);
}
}
@@ -959,33 +957,31 @@ KClientPort* KernelCore::CreateNamedServicePort(std::string name) {
return impl->CreateNamedServicePort(std::move(name));
}
-void KernelCore::RegisterServerSession(KServerSession* server_session) {
- std::lock_guard lk(impl->server_sessions_lock);
- impl->server_sessions.insert(server_session);
+void KernelCore::RegisterServerObject(KAutoObject* server_object) {
+ impl->RegisterServerObject(server_object);
}
-void KernelCore::UnregisterServerSession(KServerSession* server_session) {
- std::lock_guard lk(impl->server_sessions_lock);
- impl->server_sessions.erase(server_session);
+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);
}
@@ -1033,6 +1029,10 @@ KThread* KernelCore::GetCurrentEmuThread() const {
return impl->GetCurrentEmuThread();
}
+void KernelCore::SetCurrentEmuThread(KThread* thread) {
+ impl->SetCurrentEmuThread(thread);
+}
+
KMemoryManager& KernelCore::MemoryManager() {
return *impl->memory_manager;
}
@@ -1041,14 +1041,6 @@ const KMemoryManager& KernelCore::MemoryManager() const {
return *impl->memory_manager;
}
-KSlabHeap<Page>& KernelCore::GetUserSlabHeapPages() {
- return *impl->user_slab_heap_pages;
-}
-
-const KSlabHeap<Page>& KernelCore::GetUserSlabHeapPages() const {
- return *impl->user_slab_heap_pages;
-}
-
Kernel::KSharedMemory& KernelCore::GetHidSharedMem() {
return *impl->hid_shared_mem;
}
@@ -1081,22 +1073,38 @@ const Kernel::KSharedMemory& KernelCore::GetTimeSharedMem() const {
return *impl->time_shared_mem;
}
-void KernelCore::Suspend(bool in_suspention) {
- const bool should_suspend = exception_exited || in_suspention;
- {
- KScopedSchedulerLock lock(*this);
- const auto state = should_suspend ? ThreadState::Runnable : ThreadState::Waiting;
- for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
- impl->suspend_threads[core_id]->SetState(state);
- impl->suspend_threads[core_id]->SetWaitReasonForDebugging(
- ThreadWaitReasonForDebugging::Suspended);
- if (!should_suspend) {
- impl->suspend_threads[core_id]->DisableDispatch();
+Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() {
+ return *impl->hidbus_shared_mem;
+}
+
+const Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() const {
+ return *impl->hidbus_shared_mem;
+}
+
+void KernelCore::Suspend(bool suspended) {
+ const bool should_suspend{exception_exited || suspended};
+ const auto activity = should_suspend ? ProcessActivity::Paused : ProcessActivity::Runnable;
+
+ for (auto* process : GetProcessList()) {
+ process->SetActivity(activity);
+
+ if (should_suspend) {
+ // Wait for execution to stop
+ for (auto* thread : process->GetThreadList()) {
+ thread->WaitUntilSuspended();
}
}
}
}
+void KernelCore::ShutdownCores() {
+ KScopedSchedulerLock lk{*this};
+
+ for (auto* thread : impl->shutdown_threads) {
+ void(thread->Run());
+ }
+}
+
bool KernelCore::IsMulticore() const {
return impl->is_multicore;
}
@@ -1122,6 +1130,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);
}
@@ -1142,6 +1154,10 @@ const KWorkerTaskManager& KernelCore::WorkerTaskManager() const {
return impl->worker_task_manager;
}
+const KMemoryLayout& KernelCore::MemoryLayout() const {
+ return *impl->memory_layout;
+}
+
bool KernelCore::IsPhantomModeForSingleCore() const {
return impl->IsPhantomModeForSingleCore();
}
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 0e04fc3bb..bcf016a97 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,15 +9,12 @@
#include <string>
#include <unordered_map>
#include <vector>
-#include "core/arm/cpu_interrupt_handler.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_slab_heap.h"
-#include "core/hle/kernel/memory_types.h"
#include "core/hle/kernel/svc_common.h"
namespace Core {
-class CPUInterruptHandler;
class ExclusiveMonitor;
class System;
} // namespace Core
@@ -41,7 +37,9 @@ class KClientSession;
class KEvent;
class KHandleTable;
class KLinkedListNode;
+class KMemoryLayout;
class KMemoryManager;
+class KPageBuffer;
class KPort;
class KProcess;
class KResourceLimit;
@@ -51,6 +49,7 @@ class KSession;
class KSharedMemory;
class KSharedMemoryInfo;
class KThread;
+class KThreadLocalPage;
class KTransferMemory;
class KWorkerTaskManager;
class KWritableEvent;
@@ -108,6 +107,9 @@ public:
/// Clears all resources in use by the kernel instance.
void Shutdown();
+ /// Close all active services in use by the kernel instance.
+ void CloseServices();
+
/// Retrieves a shared pointer to the system resource limit instance.
const KResourceLimit* GetSystemResourceLimit() const;
@@ -179,10 +181,6 @@ public:
const KAutoObjectWithListContainer& ObjectListContainer() const;
- std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& Interrupts();
-
- const std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& Interrupts() const;
-
void InvalidateAllInstructionCaches();
void InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size);
@@ -193,13 +191,13 @@ public:
/// Opens a port to a service previously registered with RegisterNamedService.
KClientPort* CreateNamedServicePort(std::string name);
- /// Registers a server session with the gobal emulation state, to be freed on shutdown. This is
- /// necessary because we do not emulate processes for HLE sessions.
- void RegisterServerSession(KServerSession* server_session);
+ /// 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 previously registered with RegisterServerSession when it was
- /// destroyed during the current emulation session.
- void UnregisterServerSession(KServerSession* server_session);
+ /// 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.
@@ -223,6 +221,9 @@ public:
/// Gets the current host_thread/guest_thread pointer.
KThread* GetCurrentEmuThread() const;
+ /// Sets the current guest_thread pointer.
+ void SetCurrentEmuThread(KThread* thread);
+
/// Gets the current host_thread handle.
u32 GetCurrentHostThreadID() const;
@@ -238,12 +239,6 @@ public:
/// Gets the virtual memory manager for the kernel.
const KMemoryManager& MemoryManager() const;
- /// Gets the slab heap allocated for user space pages.
- KSlabHeap<Page>& GetUserSlabHeapPages();
-
- /// Gets the slab heap allocated for user space pages.
- const KSlabHeap<Page>& GetUserSlabHeapPages() const;
-
/// Gets the shared memory object for HID services.
Kernel::KSharedMemory& GetHidSharedMem();
@@ -268,12 +263,21 @@ public:
/// Gets the shared memory object for Time services.
const Kernel::KSharedMemory& GetTimeSharedMem() const;
- /// Suspend/unsuspend the OS.
- void Suspend(bool in_suspention);
+ /// Gets the shared memory object for HIDBus services.
+ Kernel::KSharedMemory& GetHidBusSharedMem();
- /// Exceptional exit the OS.
+ /// Gets the shared memory object for HIDBus services.
+ const Kernel::KSharedMemory& GetHidBusSharedMem() const;
+
+ /// Suspend/unsuspend all processes.
+ void Suspend(bool suspend);
+
+ /// Exceptional exit all processes.
void ExceptionalExit();
+ /// Notify emulated CPU cores to shut down.
+ void ShutdownCores();
+
bool IsMulticore() const;
bool IsShuttingDown() const;
@@ -283,9 +287,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.
@@ -293,6 +299,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.
@@ -335,6 +349,10 @@ public:
return slab_heap_container->writeable_event;
} else if constexpr (std::is_same_v<T, KCodeMemory>) {
return slab_heap_container->code_memory;
+ } else if constexpr (std::is_same_v<T, KPageBuffer>) {
+ return slab_heap_container->page_buffer;
+ } else if constexpr (std::is_same_v<T, KThreadLocalPage>) {
+ return slab_heap_container->thread_local_page;
}
}
@@ -350,6 +368,9 @@ public:
/// Gets the current worker task manager, used for dispatching KThread/KProcess tasks.
const KWorkerTaskManager& WorkerTaskManager() const;
+ /// Gets the memory layout.
+ const KMemoryLayout& MemoryLayout() const;
+
private:
friend class KProcess;
friend class KThread;
@@ -393,6 +414,8 @@ private:
KSlabHeap<KTransferMemory> transfer_memory;
KSlabHeap<KWritableEvent> writeable_event;
KSlabHeap<KCodeMemory> code_memory;
+ KSlabHeap<KPageBuffer> page_buffer;
+ KSlabHeap<KThreadLocalPage> thread_local_page;
};
std::unique_ptr<SlabHeapContainer> slab_heap_container;
diff --git a/src/core/hle/kernel/memory_types.h b/src/core/hle/kernel/memory_types.h
index d458f0eca..3975507bd 100644
--- a/src/core/hle/kernel/memory_types.h
+++ b/src/core/hle/kernel/memory_types.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp
index 7477668e4..d4375962f 100644
--- a/src/core/hle/kernel/physical_core.cpp
+++ b/src/core/hle/kernel/physical_core.cpp
@@ -1,9 +1,6 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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"
#include "core/core.h"
@@ -13,16 +10,14 @@
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>()} {
+PhysicalCore::PhysicalCore(std::size_t core_index_, Core::System& system_, KScheduler& scheduler_)
+ : core_index{core_index_}, system{system_}, scheduler{scheduler_} {
#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.
auto& kernel = system.Kernel();
arm_interface = std::make_unique<Core::ARM_Dynarmic_64>(
- system, interrupts, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index);
+ system, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index);
#else
#error Platform not supported yet.
#endif
@@ -36,7 +31,7 @@ void PhysicalCore::Initialize([[maybe_unused]] bool is_64_bit) {
if (!is_64_bit) {
// We already initialized a 64-bit core, replace with a 32-bit one.
arm_interface = std::make_unique<Core::ARM_Dynarmic_32>(
- system, interrupts, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index);
+ system, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index);
}
#else
#error Platform not supported yet.
@@ -45,26 +40,30 @@ void PhysicalCore::Initialize([[maybe_unused]] bool is_64_bit) {
void PhysicalCore::Run() {
arm_interface->Run();
+ arm_interface->ClearExclusiveState();
}
void PhysicalCore::Idle() {
- interrupts[core_index].AwaitInterrupt();
+ std::unique_lock lk{guard};
+ on_interrupt.wait(lk, [this] { return is_interrupted; });
}
bool PhysicalCore::IsInterrupted() const {
- return interrupts[core_index].IsInterrupted();
+ return is_interrupted;
}
void PhysicalCore::Interrupt() {
- guard->lock();
- interrupts[core_index].SetInterrupt(true);
- guard->unlock();
+ std::unique_lock lk{guard};
+ is_interrupted = true;
+ arm_interface->SignalInterrupt();
+ on_interrupt.notify_all();
}
void PhysicalCore::ClearInterrupt() {
- guard->lock();
- interrupts[core_index].SetInterrupt(false);
- guard->unlock();
+ std::unique_lock lk{guard};
+ is_interrupted = false;
+ arm_interface->ClearInterrupt();
+ on_interrupt.notify_all();
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/physical_core.h b/src/core/hle/kernel/physical_core.h
index 16a032e89..2fc8d4be2 100644
--- a/src/core/hle/kernel/physical_core.h
+++ b/src/core/hle/kernel/physical_core.h
@@ -1,24 +1,19 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <memory>
+#include <mutex>
#include "core/arm/arm_interface.h"
-namespace Common {
-class SpinLock;
-}
-
namespace Kernel {
class KScheduler;
} // namespace Kernel
namespace Core {
-class CPUInterruptHandler;
class ExclusiveMonitor;
class System;
} // namespace Core
@@ -27,15 +22,11 @@ namespace Kernel {
class PhysicalCore {
public:
- PhysicalCore(std::size_t core_index_, Core::System& system_, KScheduler& scheduler_,
- Core::CPUInterrupts& interrupts_);
+ PhysicalCore(std::size_t core_index_, Core::System& system_, KScheduler& scheduler_);
~PhysicalCore();
- PhysicalCore(const PhysicalCore&) = delete;
- PhysicalCore& operator=(const PhysicalCore&) = delete;
-
- PhysicalCore(PhysicalCore&&) = default;
- PhysicalCore& operator=(PhysicalCore&&) = delete;
+ YUZU_NON_COPYABLE(PhysicalCore);
+ YUZU_NON_MOVEABLE(PhysicalCore);
/// Initialize the core for the specified parameters.
void Initialize(bool is_64_bit);
@@ -90,9 +81,11 @@ private:
const std::size_t core_index;
Core::System& system;
Kernel::KScheduler& scheduler;
- Core::CPUInterrupts& interrupts;
- std::unique_ptr<Common::SpinLock> guard;
+
+ std::mutex guard;
+ std::condition_variable on_interrupt;
std::unique_ptr<Core::ARM_Interface> arm_interface;
+ bool is_interrupted;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/physical_memory.h b/src/core/hle/kernel/physical_memory.h
index 7a0266780..253fa4563 100644
--- a/src/core/hle/kernel/physical_memory.h
+++ b/src/core/hle/kernel/physical_memory.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/process_capability.cpp b/src/core/hle/kernel/process_capability.cpp
index 31a0867d3..773319ad8 100644
--- a/src/core/hle/kernel/process_capability.cpp
+++ b/src/core/hle/kernel/process_capability.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <bit>
@@ -69,9 +68,9 @@ u32 GetFlagBitOffset(CapabilityType type) {
} // Anonymous namespace
-ResultCode ProcessCapabilities::InitializeForKernelProcess(const u32* capabilities,
- std::size_t num_capabilities,
- KPageTable& page_table) {
+Result ProcessCapabilities::InitializeForKernelProcess(const u32* capabilities,
+ std::size_t num_capabilities,
+ KPageTable& page_table) {
Clear();
// Allow all cores and priorities.
@@ -82,9 +81,9 @@ ResultCode ProcessCapabilities::InitializeForKernelProcess(const u32* capabiliti
return ParseCapabilities(capabilities, num_capabilities, page_table);
}
-ResultCode ProcessCapabilities::InitializeForUserProcess(const u32* capabilities,
- std::size_t num_capabilities,
- KPageTable& page_table) {
+Result ProcessCapabilities::InitializeForUserProcess(const u32* capabilities,
+ std::size_t num_capabilities,
+ KPageTable& page_table) {
Clear();
return ParseCapabilities(capabilities, num_capabilities, page_table);
@@ -108,9 +107,8 @@ void ProcessCapabilities::InitializeForMetadatalessProcess() {
can_force_debug = true;
}
-ResultCode ProcessCapabilities::ParseCapabilities(const u32* capabilities,
- std::size_t num_capabilities,
- KPageTable& page_table) {
+Result ProcessCapabilities::ParseCapabilities(const u32* capabilities, std::size_t num_capabilities,
+ KPageTable& page_table) {
u32 set_flags = 0;
u32 set_svc_bits = 0;
@@ -156,8 +154,8 @@ ResultCode ProcessCapabilities::ParseCapabilities(const u32* capabilities,
return ResultSuccess;
}
-ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits,
- u32 flag, KPageTable& page_table) {
+Result ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits, u32 flag,
+ KPageTable& page_table) {
const auto type = GetCapabilityType(flag);
if (type == CapabilityType::Unset) {
@@ -225,7 +223,7 @@ void ProcessCapabilities::Clear() {
can_force_debug = false;
}
-ResultCode ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) {
+Result ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) {
if (priority_mask != 0 || core_mask != 0) {
LOG_ERROR(Kernel, "Core or priority mask are not zero! priority_mask={}, core_mask={}",
priority_mask, core_mask);
@@ -267,7 +265,7 @@ ResultCode ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) {
return ResultSuccess;
}
-ResultCode ProcessCapabilities::HandleSyscallFlags(u32& set_svc_bits, u32 flags) {
+Result ProcessCapabilities::HandleSyscallFlags(u32& set_svc_bits, u32 flags) {
const u32 index = flags >> 29;
const u32 svc_bit = 1U << index;
@@ -291,23 +289,23 @@ ResultCode ProcessCapabilities::HandleSyscallFlags(u32& set_svc_bits, u32 flags)
return ResultSuccess;
}
-ResultCode ProcessCapabilities::HandleMapPhysicalFlags(u32 flags, u32 size_flags,
- KPageTable& page_table) {
+Result ProcessCapabilities::HandleMapPhysicalFlags(u32 flags, u32 size_flags,
+ KPageTable& page_table) {
// TODO(Lioncache): Implement once the memory manager can handle this.
return ResultSuccess;
}
-ResultCode ProcessCapabilities::HandleMapIOFlags(u32 flags, KPageTable& page_table) {
+Result ProcessCapabilities::HandleMapIOFlags(u32 flags, KPageTable& page_table) {
// TODO(Lioncache): Implement once the memory manager can handle this.
return ResultSuccess;
}
-ResultCode ProcessCapabilities::HandleMapRegionFlags(u32 flags, KPageTable& page_table) {
+Result ProcessCapabilities::HandleMapRegionFlags(u32 flags, KPageTable& page_table) {
// TODO(Lioncache): Implement once the memory manager can handle this.
return ResultSuccess;
}
-ResultCode ProcessCapabilities::HandleInterruptFlags(u32 flags) {
+Result ProcessCapabilities::HandleInterruptFlags(u32 flags) {
constexpr u32 interrupt_ignore_value = 0x3FF;
const u32 interrupt0 = (flags >> 12) & 0x3FF;
const u32 interrupt1 = (flags >> 22) & 0x3FF;
@@ -334,7 +332,7 @@ ResultCode ProcessCapabilities::HandleInterruptFlags(u32 flags) {
return ResultSuccess;
}
-ResultCode ProcessCapabilities::HandleProgramTypeFlags(u32 flags) {
+Result ProcessCapabilities::HandleProgramTypeFlags(u32 flags) {
const u32 reserved = flags >> 17;
if (reserved != 0) {
LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved);
@@ -345,7 +343,7 @@ ResultCode ProcessCapabilities::HandleProgramTypeFlags(u32 flags) {
return ResultSuccess;
}
-ResultCode ProcessCapabilities::HandleKernelVersionFlags(u32 flags) {
+Result ProcessCapabilities::HandleKernelVersionFlags(u32 flags) {
// Yes, the internal member variable is checked in the actual kernel here.
// This might look odd for options that are only allowed to be initialized
// just once, however the kernel has a separate initialization function for
@@ -365,7 +363,7 @@ ResultCode ProcessCapabilities::HandleKernelVersionFlags(u32 flags) {
return ResultSuccess;
}
-ResultCode ProcessCapabilities::HandleHandleTableFlags(u32 flags) {
+Result ProcessCapabilities::HandleHandleTableFlags(u32 flags) {
const u32 reserved = flags >> 26;
if (reserved != 0) {
LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved);
@@ -376,7 +374,7 @@ ResultCode ProcessCapabilities::HandleHandleTableFlags(u32 flags) {
return ResultSuccess;
}
-ResultCode ProcessCapabilities::HandleDebugFlags(u32 flags) {
+Result ProcessCapabilities::HandleDebugFlags(u32 flags) {
const u32 reserved = flags >> 19;
if (reserved != 0) {
LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved);
diff --git a/src/core/hle/kernel/process_capability.h b/src/core/hle/kernel/process_capability.h
index a9b44325b..ff05dc5ff 100644
--- a/src/core/hle/kernel/process_capability.h
+++ b/src/core/hle/kernel/process_capability.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,7 +7,7 @@
#include "common/common_types.h"
-union ResultCode;
+union Result;
namespace Kernel {
@@ -87,8 +86,8 @@ public:
/// @returns ResultSuccess if this capabilities instance was able to be initialized,
/// otherwise, an error code upon failure.
///
- ResultCode InitializeForKernelProcess(const u32* capabilities, std::size_t num_capabilities,
- KPageTable& page_table);
+ Result InitializeForKernelProcess(const u32* capabilities, std::size_t num_capabilities,
+ KPageTable& page_table);
/// Initializes this process capabilities instance for a userland process.
///
@@ -100,8 +99,8 @@ public:
/// @returns ResultSuccess if this capabilities instance was able to be initialized,
/// otherwise, an error code upon failure.
///
- ResultCode InitializeForUserProcess(const u32* capabilities, std::size_t num_capabilities,
- KPageTable& page_table);
+ Result InitializeForUserProcess(const u32* capabilities, std::size_t num_capabilities,
+ KPageTable& page_table);
/// Initializes this process capabilities instance for a process that does not
/// have any metadata to parse.
@@ -186,8 +185,8 @@ private:
///
/// @return ResultSuccess if no errors occur, otherwise an error code.
///
- ResultCode ParseCapabilities(const u32* capabilities, std::size_t num_capabilities,
- KPageTable& page_table);
+ Result ParseCapabilities(const u32* capabilities, std::size_t num_capabilities,
+ KPageTable& page_table);
/// Attempts to parse a capability descriptor that is only represented by a
/// single flag set.
@@ -201,8 +200,8 @@ private:
///
/// @return ResultSuccess if no errors occurred, otherwise an error code.
///
- ResultCode ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits, u32 flag,
- KPageTable& page_table);
+ Result ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits, u32 flag,
+ KPageTable& page_table);
/// Clears the internal state of this process capability instance. Necessary,
/// to have a sane starting point due to us allowing running executables without
@@ -220,34 +219,34 @@ private:
void Clear();
/// Handles flags related to the priority and core number capability flags.
- ResultCode HandlePriorityCoreNumFlags(u32 flags);
+ Result HandlePriorityCoreNumFlags(u32 flags);
/// Handles flags related to determining the allowable SVC mask.
- ResultCode HandleSyscallFlags(u32& set_svc_bits, u32 flags);
+ Result HandleSyscallFlags(u32& set_svc_bits, u32 flags);
/// Handles flags related to mapping physical memory pages.
- ResultCode HandleMapPhysicalFlags(u32 flags, u32 size_flags, KPageTable& page_table);
+ Result HandleMapPhysicalFlags(u32 flags, u32 size_flags, KPageTable& page_table);
/// Handles flags related to mapping IO pages.
- ResultCode HandleMapIOFlags(u32 flags, KPageTable& page_table);
+ Result HandleMapIOFlags(u32 flags, KPageTable& page_table);
/// Handles flags related to mapping physical memory regions.
- ResultCode HandleMapRegionFlags(u32 flags, KPageTable& page_table);
+ Result HandleMapRegionFlags(u32 flags, KPageTable& page_table);
/// Handles flags related to the interrupt capability flags.
- ResultCode HandleInterruptFlags(u32 flags);
+ Result HandleInterruptFlags(u32 flags);
/// Handles flags related to the program type.
- ResultCode HandleProgramTypeFlags(u32 flags);
+ Result HandleProgramTypeFlags(u32 flags);
/// Handles flags related to the handle table size.
- ResultCode HandleHandleTableFlags(u32 flags);
+ Result HandleHandleTableFlags(u32 flags);
/// Handles flags related to the kernel version capability flags.
- ResultCode HandleKernelVersionFlags(u32 flags);
+ Result HandleKernelVersionFlags(u32 flags);
/// Handles flags related to debug-specific capabilities.
- ResultCode HandleDebugFlags(u32 flags);
+ Result HandleDebugFlags(u32 flags);
SyscallCapabilities svc_capabilities;
InterruptCapabilities interrupt_capabilities;
diff --git a/src/core/hle/kernel/service_thread.cpp b/src/core/hle/kernel/service_thread.cpp
index 4eb3a5988..d23d76706 100644
--- a/src/core/hle/kernel/service_thread.cpp
+++ b/src/core/hle/kernel/service_thread.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <condition_variable>
#include <functional>
@@ -37,7 +36,7 @@ ServiceThread::Impl::Impl(KernelCore& kernel, std::size_t num_threads, const std
: service_name{name} {
for (std::size_t i = 0; i < num_threads; ++i) {
threads.emplace_back([this, &kernel](std::stop_token stop_token) {
- Common::SetCurrentThreadName(std::string{"yuzu:HleService:" + service_name}.c_str());
+ Common::SetCurrentThreadName(std::string{service_name}.c_str());
// Wait for first request before trying to acquire a render context
{
@@ -49,12 +48,9 @@ ServiceThread::Impl::Impl(KernelCore& kernel, std::size_t num_threads, const std
return;
}
+ // Allocate a dummy guest thread for this host thread.
kernel.RegisterHostThread();
- // Ensure the dummy thread allocated for this host thread is closed on exit.
- auto* dummy_thread = kernel.GetCurrentEmuThread();
- SCOPE_EXIT({ dummy_thread->Close(); });
-
while (true) {
std::function<void()> task;
diff --git a/src/core/hle/kernel/service_thread.h b/src/core/hle/kernel/service_thread.h
index 6a7fd7c56..c5896f2bd 100644
--- a/src/core/hle/kernel/service_thread.h
+++ b/src/core/hle/kernel/service_thread.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/slab_helpers.h b/src/core/hle/kernel/slab_helpers.h
index f1c11256e..299a981a8 100644
--- a/src/core/hle/kernel/slab_helpers.h
+++ b/src/core/hle/kernel/slab_helpers.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -59,7 +58,7 @@ class KAutoObjectWithSlabHeapAndContainer : public Base {
private:
static Derived* Allocate(KernelCore& kernel) {
- return kernel.SlabHeap<Derived>().AllocateWithKernel(kernel);
+ return kernel.SlabHeap<Derived>().Allocate(kernel);
}
static void Free(KernelCore& kernel, Derived* obj) {
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 9387373c1..27e5a805d 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cinttypes>
@@ -16,6 +15,7 @@
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/debugger/debugger.h"
#include "core/hle/kernel/k_client_port.h"
#include "core/hle/kernel/k_client_session.h"
#include "core/hle/kernel/k_code_memory.h"
@@ -58,8 +58,8 @@ constexpr bool IsValidAddressRange(VAddr address, u64 size) {
// Helper function that performs the common sanity checks for svcMapMemory
// and svcUnmapMemory. This is doable, as both functions perform their sanitizing
// in the same order.
-ResultCode MapUnmapMemorySanityChecks(const KPageTable& manager, VAddr dst_addr, VAddr src_addr,
- u64 size) {
+Result MapUnmapMemorySanityChecks(const KPageTable& manager, VAddr dst_addr, VAddr src_addr,
+ u64 size) {
if (!Common::Is4KBAligned(dst_addr)) {
LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, 0x{:016X}", dst_addr);
return ResultInvalidAddress;
@@ -135,7 +135,7 @@ enum class ResourceLimitValueType {
} // Anonymous namespace
/// Set the process heap to a given Size. It can both extend and shrink the heap.
-static ResultCode SetHeapSize(Core::System& system, VAddr* out_address, u64 size) {
+static Result SetHeapSize(Core::System& system, VAddr* out_address, u64 size) {
LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", size);
// Validate size.
@@ -148,9 +148,9 @@ static ResultCode SetHeapSize(Core::System& system, VAddr* out_address, u64 size
return ResultSuccess;
}
-static ResultCode SetHeapSize32(Core::System& system, u32* heap_addr, u32 heap_size) {
+static Result SetHeapSize32(Core::System& system, u32* heap_addr, u32 heap_size) {
VAddr temp_heap_addr{};
- const ResultCode result{SetHeapSize(system, &temp_heap_addr, heap_size)};
+ const Result result{SetHeapSize(system, &temp_heap_addr, heap_size)};
*heap_addr = static_cast<u32>(temp_heap_addr);
return result;
}
@@ -166,8 +166,8 @@ constexpr bool IsValidSetMemoryPermission(MemoryPermission perm) {
}
}
-static ResultCode SetMemoryPermission(Core::System& system, VAddr address, u64 size,
- MemoryPermission perm) {
+static Result SetMemoryPermission(Core::System& system, VAddr address, u64 size,
+ MemoryPermission perm) {
LOG_DEBUG(Kernel_SVC, "called, address=0x{:016X}, size=0x{:X}, perm=0x{:08X", address, size,
perm);
@@ -188,8 +188,8 @@ static ResultCode SetMemoryPermission(Core::System& system, VAddr address, u64 s
return page_table.SetMemoryPermission(address, size, perm);
}
-static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask,
- u32 attr) {
+static Result SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask,
+ u32 attr) {
LOG_DEBUG(Kernel_SVC,
"called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address,
size, mask, attr);
@@ -213,19 +213,19 @@ static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 si
return page_table.SetMemoryAttribute(address, size, mask, attr);
}
-static ResultCode SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask,
- u32 attr) {
+static Result SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask,
+ u32 attr) {
return SetMemoryAttribute(system, address, size, mask, attr);
}
/// Maps a memory range into a different range.
-static ResultCode MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) {
+static Result MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) {
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
src_addr, size);
auto& page_table{system.Kernel().CurrentProcess()->PageTable()};
- if (const ResultCode result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
+ if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
result.IsError()) {
return result;
}
@@ -233,18 +233,18 @@ static ResultCode MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr
return page_table.MapMemory(dst_addr, src_addr, size);
}
-static ResultCode MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
+static Result MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
return MapMemory(system, dst_addr, src_addr, size);
}
/// Unmaps a region that was previously mapped with svcMapMemory
-static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) {
+static Result UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) {
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
src_addr, size);
auto& page_table{system.Kernel().CurrentProcess()->PageTable()};
- if (const ResultCode result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
+ if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
result.IsError()) {
return result;
}
@@ -252,12 +252,12 @@ static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_ad
return page_table.UnmapMemory(dst_addr, src_addr, size);
}
-static ResultCode UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
+static Result UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
return UnmapMemory(system, dst_addr, src_addr, size);
}
/// Connect to an OS service given the port name, returns the handle to the port to out
-static ResultCode ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_name_address) {
+static Result ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_name_address) {
auto& memory = system.Memory();
if (!memory.IsValidVirtualAddress(port_name_address)) {
LOG_ERROR(Kernel_SVC,
@@ -307,14 +307,14 @@ static ResultCode ConnectToNamedPort(Core::System& system, Handle* out, VAddr po
return ResultSuccess;
}
-static ResultCode ConnectToNamedPort32(Core::System& system, Handle* out_handle,
- u32 port_name_address) {
+static Result ConnectToNamedPort32(Core::System& system, Handle* out_handle,
+ u32 port_name_address) {
return ConnectToNamedPort(system, out_handle, port_name_address);
}
/// Makes a blocking IPC call to an OS service.
-static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
+static Result SendSyncRequest(Core::System& system, Handle handle) {
auto& kernel = system.Kernel();
// Create the wait queue.
@@ -327,7 +327,6 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
- auto thread = kernel.CurrentScheduler()->GetCurrentThread();
{
KScopedSchedulerLock lock(kernel);
@@ -337,15 +336,15 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
session->SendSyncRequest(&GetCurrentThread(kernel), system.Memory(), system.CoreTiming());
}
- return thread->GetWaitResult();
+ return GetCurrentThread(kernel).GetWaitResult();
}
-static ResultCode SendSyncRequest32(Core::System& system, Handle handle) {
+static Result SendSyncRequest32(Core::System& system, Handle handle) {
return SendSyncRequest(system, handle);
}
/// Get the ID for the specified thread.
-static ResultCode GetThreadId(Core::System& system, u64* out_thread_id, Handle thread_handle) {
+static Result GetThreadId(Core::System& system, u64* out_thread_id, Handle thread_handle) {
// Get the thread from its handle.
KScopedAutoObject thread =
system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle);
@@ -356,10 +355,10 @@ static ResultCode GetThreadId(Core::System& system, u64* out_thread_id, Handle t
return ResultSuccess;
}
-static ResultCode GetThreadId32(Core::System& system, u32* out_thread_id_low,
- u32* out_thread_id_high, Handle thread_handle) {
+static Result GetThreadId32(Core::System& system, u32* out_thread_id_low, u32* out_thread_id_high,
+ Handle thread_handle) {
u64 out_thread_id{};
- const ResultCode result{GetThreadId(system, &out_thread_id, thread_handle)};
+ const Result result{GetThreadId(system, &out_thread_id, thread_handle)};
*out_thread_id_low = static_cast<u32>(out_thread_id >> 32);
*out_thread_id_high = static_cast<u32>(out_thread_id & std::numeric_limits<u32>::max());
@@ -368,7 +367,7 @@ static ResultCode GetThreadId32(Core::System& system, u32* out_thread_id_low,
}
/// Gets the ID of the specified process or a specified thread's owning process.
-static ResultCode GetProcessId(Core::System& system, u64* out_process_id, Handle handle) {
+static Result GetProcessId(Core::System& system, u64* out_process_id, Handle handle) {
LOG_DEBUG(Kernel_SVC, "called handle=0x{:08X}", handle);
// Get the object from the handle table.
@@ -399,8 +398,8 @@ static ResultCode GetProcessId(Core::System& system, u64* out_process_id, Handle
return ResultSuccess;
}
-static ResultCode GetProcessId32(Core::System& system, u32* out_process_id_low,
- u32* out_process_id_high, Handle handle) {
+static Result GetProcessId32(Core::System& system, u32* out_process_id_low,
+ u32* out_process_id_high, Handle handle) {
u64 out_process_id{};
const auto result = GetProcessId(system, &out_process_id, handle);
*out_process_id_low = static_cast<u32>(out_process_id);
@@ -409,8 +408,8 @@ static ResultCode GetProcessId32(Core::System& system, u32* out_process_id_low,
}
/// Wait for the given handles to synchronize, timeout after the specified nanoseconds
-static ResultCode WaitSynchronization(Core::System& system, s32* index, VAddr handles_address,
- s32 num_handles, s64 nano_seconds) {
+static Result WaitSynchronization(Core::System& system, s32* index, VAddr handles_address,
+ s32 num_handles, s64 nano_seconds) {
LOG_TRACE(Kernel_SVC, "called handles_address=0x{:X}, num_handles={}, nano_seconds={}",
handles_address, num_handles, nano_seconds);
@@ -445,14 +444,14 @@ static ResultCode WaitSynchronization(Core::System& system, s32* index, VAddr ha
nano_seconds);
}
-static ResultCode WaitSynchronization32(Core::System& system, u32 timeout_low, u32 handles_address,
- s32 num_handles, u32 timeout_high, s32* index) {
+static Result WaitSynchronization32(Core::System& system, u32 timeout_low, u32 handles_address,
+ s32 num_handles, u32 timeout_high, s32* index) {
const s64 nano_seconds{(static_cast<s64>(timeout_high) << 32) | static_cast<s64>(timeout_low)};
return WaitSynchronization(system, index, handles_address, num_handles, nano_seconds);
}
/// Resumes a thread waiting on WaitSynchronization
-static ResultCode CancelSynchronization(Core::System& system, Handle handle) {
+static Result CancelSynchronization(Core::System& system, Handle handle) {
LOG_TRACE(Kernel_SVC, "called handle=0x{:X}", handle);
// Get the thread from its handle.
@@ -465,13 +464,12 @@ static ResultCode CancelSynchronization(Core::System& system, Handle handle) {
return ResultSuccess;
}
-static ResultCode CancelSynchronization32(Core::System& system, Handle handle) {
+static Result CancelSynchronization32(Core::System& system, Handle handle) {
return CancelSynchronization(system, handle);
}
/// Attempts to locks a mutex
-static ResultCode ArbitrateLock(Core::System& system, Handle thread_handle, VAddr address,
- u32 tag) {
+static Result ArbitrateLock(Core::System& system, Handle thread_handle, VAddr address, u32 tag) {
LOG_TRACE(Kernel_SVC, "called thread_handle=0x{:08X}, address=0x{:X}, tag=0x{:08X}",
thread_handle, address, tag);
@@ -489,13 +487,12 @@ static ResultCode ArbitrateLock(Core::System& system, Handle thread_handle, VAdd
return system.Kernel().CurrentProcess()->WaitForAddress(thread_handle, address, tag);
}
-static ResultCode ArbitrateLock32(Core::System& system, Handle thread_handle, u32 address,
- u32 tag) {
+static Result ArbitrateLock32(Core::System& system, Handle thread_handle, u32 address, u32 tag) {
return ArbitrateLock(system, thread_handle, address, tag);
}
/// Unlock a mutex
-static ResultCode ArbitrateUnlock(Core::System& system, VAddr address) {
+static Result ArbitrateUnlock(Core::System& system, VAddr address) {
LOG_TRACE(Kernel_SVC, "called address=0x{:X}", address);
// Validate the input address.
@@ -513,7 +510,7 @@ static ResultCode ArbitrateUnlock(Core::System& system, VAddr address) {
return system.Kernel().CurrentProcess()->SignalToAddress(address);
}
-static ResultCode ArbitrateUnlock32(Core::System& system, u32 address) {
+static Result ArbitrateUnlock32(Core::System& system, u32 address) {
return ArbitrateUnlock(system, address);
}
@@ -624,10 +621,16 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
handle_debug_buffer(info1, info2);
- auto* const current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
+ auto* const current_thread = GetCurrentThreadPointer(system.Kernel());
const auto thread_processor_id = current_thread->GetActiveCore();
system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
}
+
+ if (system.DebuggerEnabled()) {
+ auto* thread = system.Kernel().GetCurrentEmuThread();
+ system.GetDebugger().NotifyThreadStopped(thread);
+ thread->RequestSuspend(Kernel::SuspendType::Debug);
+ }
}
static void Break32(Core::System& system, u32 reason, u32 info1, u32 info2) {
@@ -645,9 +648,13 @@ static void OutputDebugString(Core::System& system, VAddr address, u64 len) {
LOG_DEBUG(Debug_Emulated, "{}", str);
}
+static void OutputDebugString32(Core::System& system, u32 address, u32 len) {
+ OutputDebugString(system, address, len);
+}
+
/// Gets system/memory information for the current process
-static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, Handle handle,
- u64 info_sub_id) {
+static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle handle,
+ u64 info_sub_id) {
LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
info_sub_id, handle);
@@ -682,6 +689,9 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, Handle
// 6.0.0+
TotalPhysicalMemoryAvailableWithoutSystemResource = 21,
TotalPhysicalMemoryUsedWithoutSystemResource = 22,
+
+ // Homebrew only
+ MesosphereCurrentProcess = 65001,
};
const auto info_id_type = static_cast<GetInfoType>(info_id);
@@ -874,10 +884,10 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, Handle
const auto& core_timing = system.CoreTiming();
const auto& scheduler = *system.Kernel().CurrentScheduler();
- const auto* const current_thread = scheduler.GetCurrentThread();
+ const auto* const current_thread = GetCurrentThreadPointer(system.Kernel());
const bool same_thread = current_thread == thread.GetPointerUnsafe();
- const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTicks();
+ const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTime();
u64 out_ticks = 0;
if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
const u64 thread_ticks = current_thread->GetCpuTime();
@@ -896,7 +906,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, Handle
// Verify the requested core is valid.
const bool core_valid =
- (info_sub_id == static_cast<u64>(-1ULL)) ||
+ (info_sub_id == 0xFFFFFFFFFFFFFFFF) ||
(info_sub_id == static_cast<u64>(system.Kernel().CurrentPhysicalCoreIndex()));
R_UNLESS(core_valid, ResultInvalidCombination);
@@ -904,18 +914,39 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, Handle
*result = system.Kernel().CurrentScheduler()->GetIdleThread()->GetCpuTime();
return ResultSuccess;
}
+ case GetInfoType::MesosphereCurrentProcess: {
+ // Verify the input handle is invalid.
+ R_UNLESS(handle == InvalidHandle, ResultInvalidHandle);
+
+ // Verify the sub-type is valid.
+ R_UNLESS(info_sub_id == 0, ResultInvalidCombination);
+
+ // Get the handle table.
+ KProcess* current_process = system.Kernel().CurrentProcess();
+ KHandleTable& handle_table = current_process->GetHandleTable();
+
+ // Get a new handle for the current process.
+ Handle tmp;
+ R_TRY(handle_table.Add(&tmp, current_process));
+
+ // Set the output.
+ *result = tmp;
+
+ // We succeeded.
+ return ResultSuccess;
+ }
default:
LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id);
return ResultInvalidEnumValue;
}
}
-static ResultCode GetInfo32(Core::System& system, u32* result_low, u32* result_high, u32 sub_id_low,
- u32 info_id, u32 handle, u32 sub_id_high) {
+static Result GetInfo32(Core::System& system, u32* result_low, u32* result_high, u32 sub_id_low,
+ u32 info_id, u32 handle, u32 sub_id_high) {
const u64 sub_id{u64{sub_id_low} | (u64{sub_id_high} << 32)};
u64 res_value{};
- const ResultCode result{GetInfo(system, &res_value, info_id, handle, sub_id)};
+ const Result result{GetInfo(system, &res_value, info_id, handle, sub_id)};
*result_high = static_cast<u32>(res_value >> 32);
*result_low = static_cast<u32>(res_value & std::numeric_limits<u32>::max());
@@ -923,7 +954,7 @@ static ResultCode GetInfo32(Core::System& system, u32* result_low, u32* result_h
}
/// Maps memory at a desired address
-static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
+static Result MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size);
if (!Common::Is4KBAligned(addr)) {
@@ -971,12 +1002,12 @@ static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size)
return page_table.MapPhysicalMemory(addr, size);
}
-static ResultCode MapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
+static Result MapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
return MapPhysicalMemory(system, addr, size);
}
/// Unmaps memory previously mapped via MapPhysicalMemory
-static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
+static Result UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size);
if (!Common::Is4KBAligned(addr)) {
@@ -1024,13 +1055,13 @@ static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size
return page_table.UnmapPhysicalMemory(addr, size);
}
-static ResultCode UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
+static Result UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
return UnmapPhysicalMemory(system, addr, size);
}
/// Sets the thread activity
-static ResultCode SetThreadActivity(Core::System& system, Handle thread_handle,
- ThreadActivity thread_activity) {
+static Result SetThreadActivity(Core::System& system, Handle thread_handle,
+ ThreadActivity thread_activity) {
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", thread_handle,
thread_activity);
@@ -1055,13 +1086,13 @@ static ResultCode SetThreadActivity(Core::System& system, Handle thread_handle,
return ResultSuccess;
}
-static ResultCode SetThreadActivity32(Core::System& system, Handle thread_handle,
- Svc::ThreadActivity thread_activity) {
+static Result SetThreadActivity32(Core::System& system, Handle thread_handle,
+ Svc::ThreadActivity thread_activity) {
return SetThreadActivity(system, thread_handle, thread_activity);
}
/// Gets the thread context
-static ResultCode GetThreadContext(Core::System& system, VAddr out_context, Handle thread_handle) {
+static Result GetThreadContext(Core::System& system, VAddr out_context, Handle thread_handle) {
LOG_DEBUG(Kernel_SVC, "called, out_context=0x{:08X}, thread_handle=0x{:X}", out_context,
thread_handle);
@@ -1093,7 +1124,7 @@ static ResultCode GetThreadContext(Core::System& system, VAddr out_context, Hand
if (thread->GetRawState() != ThreadState::Runnable) {
bool current = false;
for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) {
- if (thread.GetPointerUnsafe() == kernel.Scheduler(i).GetCurrentThread()) {
+ if (thread.GetPointerUnsafe() == kernel.Scheduler(i).GetSchedulerCurrentThread()) {
current = true;
break;
}
@@ -1118,12 +1149,12 @@ static ResultCode GetThreadContext(Core::System& system, VAddr out_context, Hand
return ResultSuccess;
}
-static ResultCode GetThreadContext32(Core::System& system, u32 out_context, Handle thread_handle) {
+static Result GetThreadContext32(Core::System& system, u32 out_context, Handle thread_handle) {
return GetThreadContext(system, out_context, thread_handle);
}
/// Gets the priority for the specified thread
-static ResultCode GetThreadPriority(Core::System& system, u32* out_priority, Handle handle) {
+static Result GetThreadPriority(Core::System& system, u32* out_priority, Handle handle) {
LOG_TRACE(Kernel_SVC, "called");
// Get the thread from its handle.
@@ -1136,12 +1167,12 @@ static ResultCode GetThreadPriority(Core::System& system, u32* out_priority, Han
return ResultSuccess;
}
-static ResultCode GetThreadPriority32(Core::System& system, u32* out_priority, Handle handle) {
+static Result GetThreadPriority32(Core::System& system, u32* out_priority, Handle handle) {
return GetThreadPriority(system, out_priority, handle);
}
/// Sets the priority for the specified thread
-static ResultCode SetThreadPriority(Core::System& system, Handle thread_handle, u32 priority) {
+static Result SetThreadPriority(Core::System& system, Handle thread_handle, u32 priority) {
// Get the current process.
KProcess& process = *system.Kernel().CurrentProcess();
@@ -1159,7 +1190,7 @@ static ResultCode SetThreadPriority(Core::System& system, Handle thread_handle,
return ResultSuccess;
}
-static ResultCode SetThreadPriority32(Core::System& system, Handle thread_handle, u32 priority) {
+static Result SetThreadPriority32(Core::System& system, Handle thread_handle, u32 priority) {
return SetThreadPriority(system, thread_handle, priority);
}
@@ -1219,8 +1250,8 @@ constexpr bool IsValidUnmapFromOwnerCodeMemoryPermission(Svc::MemoryPermission p
} // Anonymous namespace
-static ResultCode MapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address,
- u64 size, Svc::MemoryPermission map_perm) {
+static Result MapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address, u64 size,
+ Svc::MemoryPermission map_perm) {
LOG_TRACE(Kernel_SVC,
"called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
shmem_handle, address, size, map_perm);
@@ -1260,13 +1291,13 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shmem_handle, VAd
return ResultSuccess;
}
-static ResultCode MapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address,
- u32 size, Svc::MemoryPermission map_perm) {
+static Result MapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address, u32 size,
+ Svc::MemoryPermission map_perm) {
return MapSharedMemory(system, shmem_handle, address, size, map_perm);
}
-static ResultCode UnmapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address,
- u64 size) {
+static Result UnmapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address,
+ u64 size) {
// Validate the address/size.
R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
@@ -1293,13 +1324,13 @@ static ResultCode UnmapSharedMemory(Core::System& system, Handle shmem_handle, V
return ResultSuccess;
}
-static ResultCode UnmapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address,
- u32 size) {
+static Result UnmapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address,
+ u32 size) {
return UnmapSharedMemory(system, shmem_handle, address, size);
}
-static ResultCode SetProcessMemoryPermission(Core::System& system, Handle process_handle,
- VAddr address, u64 size, Svc::MemoryPermission perm) {
+static Result SetProcessMemoryPermission(Core::System& system, Handle process_handle, VAddr address,
+ u64 size, Svc::MemoryPermission perm) {
LOG_TRACE(Kernel_SVC,
"called, process_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
process_handle, address, size, perm);
@@ -1328,8 +1359,8 @@ static ResultCode SetProcessMemoryPermission(Core::System& system, Handle proces
return page_table.SetProcessMemoryPermission(address, size, perm);
}
-static ResultCode MapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle,
- VAddr src_address, u64 size) {
+static Result MapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle,
+ VAddr src_address, u64 size) {
LOG_TRACE(Kernel_SVC,
"called, dst_address=0x{:X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}",
dst_address, process_handle, src_address, size);
@@ -1358,8 +1389,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());
+ KPageGroup 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,
@@ -1368,8 +1402,8 @@ static ResultCode MapProcessMemory(Core::System& system, VAddr dst_address, Hand
return ResultSuccess;
}
-static ResultCode UnmapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle,
- VAddr src_address, u64 size) {
+static Result UnmapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle,
+ VAddr src_address, u64 size) {
LOG_TRACE(Kernel_SVC,
"called, dst_address=0x{:X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}",
dst_address, process_handle, src_address, size);
@@ -1403,9 +1437,9 @@ static ResultCode UnmapProcessMemory(Core::System& system, VAddr dst_address, Ha
return ResultSuccess;
}
-static ResultCode CreateCodeMemory(Core::System& system, Handle* out, VAddr address, size_t size) {
- LOG_TRACE(Kernel_SVC, "called, handle_out=0x{:X}, address=0x{:X}, size=0x{:X}",
- static_cast<void*>(out), address, size);
+static Result CreateCodeMemory(Core::System& system, Handle* out, VAddr address, size_t size) {
+ LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, size=0x{:X}", address, size);
+
// Get kernel instance.
auto& kernel = system.Kernel();
@@ -1438,8 +1472,12 @@ static ResultCode CreateCodeMemory(Core::System& system, Handle* out, VAddr addr
return ResultSuccess;
}
-static ResultCode ControlCodeMemory(Core::System& system, Handle code_memory_handle, u32 operation,
- VAddr address, size_t size, Svc::MemoryPermission perm) {
+static Result CreateCodeMemory32(Core::System& system, Handle* out, u32 address, u32 size) {
+ return CreateCodeMemory(system, out, address, size);
+}
+
+static Result ControlCodeMemory(Core::System& system, Handle code_memory_handle, u32 operation,
+ VAddr address, size_t size, Svc::MemoryPermission perm) {
LOG_TRACE(Kernel_SVC,
"called, code_memory_handle=0x{:X}, operation=0x{:X}, address=0x{:X}, size=0x{:X}, "
@@ -1517,9 +1555,13 @@ static ResultCode ControlCodeMemory(Core::System& system, Handle code_memory_han
return ResultSuccess;
}
-static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_address,
- VAddr page_info_address, Handle process_handle,
- VAddr address) {
+static Result ControlCodeMemory32(Core::System& system, Handle code_memory_handle, u32 operation,
+ u64 address, u64 size, Svc::MemoryPermission perm) {
+ return ControlCodeMemory(system, code_memory_handle, operation, address, size, perm);
+}
+
+static Result QueryProcessMemory(Core::System& system, VAddr memory_info_address,
+ VAddr page_info_address, Handle process_handle, VAddr address) {
LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address);
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle);
@@ -1547,8 +1589,8 @@ static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_add
return ResultSuccess;
}
-static ResultCode QueryMemory(Core::System& system, VAddr memory_info_address,
- VAddr page_info_address, VAddr query_address) {
+static Result QueryMemory(Core::System& system, VAddr memory_info_address, VAddr page_info_address,
+ VAddr query_address) {
LOG_TRACE(Kernel_SVC,
"called, memory_info_address=0x{:016X}, page_info_address=0x{:016X}, "
"query_address=0x{:016X}",
@@ -1558,13 +1600,13 @@ static ResultCode QueryMemory(Core::System& system, VAddr memory_info_address,
query_address);
}
-static ResultCode QueryMemory32(Core::System& system, u32 memory_info_address,
- u32 page_info_address, u32 query_address) {
+static Result QueryMemory32(Core::System& system, u32 memory_info_address, u32 page_info_address,
+ u32 query_address) {
return QueryMemory(system, memory_info_address, page_info_address, query_address);
}
-static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address,
- u64 src_address, u64 size) {
+static Result MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address,
+ u64 src_address, u64 size) {
LOG_DEBUG(Kernel_SVC,
"called. process_handle=0x{:08X}, dst_address=0x{:016X}, "
"src_address=0x{:016X}, size=0x{:016X}",
@@ -1631,8 +1673,8 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand
return page_table.MapCodeMemory(dst_address, src_address, size);
}
-static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_handle,
- u64 dst_address, u64 src_address, u64 size) {
+static Result UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address,
+ u64 src_address, u64 size) {
LOG_DEBUG(Kernel_SVC,
"called. process_handle=0x{:08X}, dst_address=0x{:016X}, src_address=0x{:016X}, "
"size=0x{:016X}",
@@ -1650,7 +1692,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;
}
@@ -1696,17 +1738,19 @@ 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
static void ExitProcess(Core::System& system) {
auto* current_process = system.Kernel().CurrentProcess();
- UNIMPLEMENTED();
LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID());
ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running,
"Process has already exited");
+
+ system.Exit();
}
static void ExitProcess32(Core::System& system) {
@@ -1722,8 +1766,8 @@ constexpr bool IsValidVirtualCoreId(int32_t core_id) {
} // Anonymous namespace
/// Creates a new thread
-static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg,
- VAddr stack_bottom, u32 priority, s32 core_id) {
+static Result CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg,
+ VAddr stack_bottom, u32 priority, s32 core_id) {
LOG_DEBUG(Kernel_SVC,
"called entry_point=0x{:08X}, arg=0x{:08X}, stack_bottom=0x{:08X}, "
"priority=0x{:08X}, core_id=0x{:08X}",
@@ -1794,13 +1838,13 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e
return ResultSuccess;
}
-static ResultCode CreateThread32(Core::System& system, Handle* out_handle, u32 priority,
- u32 entry_point, u32 arg, u32 stack_top, s32 processor_id) {
+static Result CreateThread32(Core::System& system, Handle* out_handle, u32 priority,
+ u32 entry_point, u32 arg, u32 stack_top, s32 processor_id) {
return CreateThread(system, out_handle, entry_point, arg, stack_top, priority, processor_id);
}
/// Starts the thread for the provided handle
-static ResultCode StartThread(Core::System& system, Handle thread_handle) {
+static Result StartThread(Core::System& system, Handle thread_handle) {
LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
// Get the thread from its handle.
@@ -1818,7 +1862,7 @@ static ResultCode StartThread(Core::System& system, Handle thread_handle) {
return ResultSuccess;
}
-static ResultCode StartThread32(Core::System& system, Handle thread_handle) {
+static Result StartThread32(Core::System& system, Handle thread_handle) {
return StartThread(system, thread_handle);
}
@@ -1826,7 +1870,7 @@ static ResultCode StartThread32(Core::System& system, Handle thread_handle) {
static void ExitThread(Core::System& system) {
LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC());
- auto* const current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
+ auto* const current_thread = GetCurrentThreadPointer(system.Kernel());
system.GlobalSchedulerContext().RemoveThread(current_thread);
current_thread->Exit();
system.Kernel().UnregisterInUseObject(current_thread);
@@ -1859,7 +1903,7 @@ static void SleepThread(Core::System& system, s64 nanoseconds) {
KScheduler::YieldToAnyThread(kernel);
} else {
// Nintendo does nothing at all if an otherwise invalid value is passed.
- UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
+ ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds);
}
}
@@ -1869,8 +1913,8 @@ static void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanosec
}
/// Wait process wide key atomic
-static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr address, VAddr cv_key,
- u32 tag, s64 timeout_ns) {
+static Result WaitProcessWideKeyAtomic(Core::System& system, VAddr address, VAddr cv_key, u32 tag,
+ s64 timeout_ns) {
LOG_TRACE(Kernel_SVC, "called address={:X}, cv_key={:X}, tag=0x{:08X}, timeout_ns={}", address,
cv_key, tag, timeout_ns);
@@ -1905,8 +1949,8 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr address,
address, Common::AlignDown(cv_key, sizeof(u32)), tag, timeout);
}
-static ResultCode WaitProcessWideKeyAtomic32(Core::System& system, u32 address, u32 cv_key, u32 tag,
- u32 timeout_ns_low, u32 timeout_ns_high) {
+static Result WaitProcessWideKeyAtomic32(Core::System& system, u32 address, u32 cv_key, u32 tag,
+ u32 timeout_ns_low, u32 timeout_ns_high) {
const auto timeout_ns = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32));
return WaitProcessWideKeyAtomic(system, address, cv_key, tag, timeout_ns);
}
@@ -1951,8 +1995,8 @@ constexpr bool IsValidArbitrationType(Svc::ArbitrationType type) {
} // namespace
// Wait for an address (via Address Arbiter)
-static ResultCode WaitForAddress(Core::System& system, VAddr address, Svc::ArbitrationType arb_type,
- s32 value, s64 timeout_ns) {
+static Result WaitForAddress(Core::System& system, VAddr address, Svc::ArbitrationType arb_type,
+ s32 value, s64 timeout_ns) {
LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, arb_type=0x{:X}, value=0x{:X}, timeout_ns={}",
address, arb_type, value, timeout_ns);
@@ -1989,15 +2033,15 @@ static ResultCode WaitForAddress(Core::System& system, VAddr address, Svc::Arbit
return system.Kernel().CurrentProcess()->WaitAddressArbiter(address, arb_type, value, timeout);
}
-static ResultCode WaitForAddress32(Core::System& system, u32 address, Svc::ArbitrationType arb_type,
- s32 value, u32 timeout_ns_low, u32 timeout_ns_high) {
+static Result WaitForAddress32(Core::System& system, u32 address, Svc::ArbitrationType arb_type,
+ s32 value, u32 timeout_ns_low, u32 timeout_ns_high) {
const auto timeout = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32));
return WaitForAddress(system, address, arb_type, value, timeout);
}
// Signals to an address (via Address Arbiter)
-static ResultCode SignalToAddress(Core::System& system, VAddr address, Svc::SignalType signal_type,
- s32 value, s32 count) {
+static Result SignalToAddress(Core::System& system, VAddr address, Svc::SignalType signal_type,
+ s32 value, s32 count) {
LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, signal_type=0x{:X}, value=0x{:X}, count=0x{:X}",
address, signal_type, value, count);
@@ -2038,8 +2082,8 @@ static void SynchronizePreemptionState(Core::System& system) {
}
}
-static ResultCode SignalToAddress32(Core::System& system, u32 address, Svc::SignalType signal_type,
- s32 value, s32 count) {
+static Result SignalToAddress32(Core::System& system, u32 address, Svc::SignalType signal_type,
+ s32 value, s32 count) {
return SignalToAddress(system, address, signal_type, value, count);
}
@@ -2077,7 +2121,7 @@ static void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high)
}
/// Close a handle
-static ResultCode CloseHandle(Core::System& system, Handle handle) {
+static Result CloseHandle(Core::System& system, Handle handle) {
LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle);
// Remove the handle.
@@ -2087,12 +2131,12 @@ static ResultCode CloseHandle(Core::System& system, Handle handle) {
return ResultSuccess;
}
-static ResultCode CloseHandle32(Core::System& system, Handle handle) {
+static Result CloseHandle32(Core::System& system, Handle handle) {
return CloseHandle(system, handle);
}
/// Clears the signaled state of an event or process.
-static ResultCode ResetSignal(Core::System& system, Handle handle) {
+static Result ResetSignal(Core::System& system, Handle handle) {
LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle);
// Get the current handle table.
@@ -2119,7 +2163,7 @@ static ResultCode ResetSignal(Core::System& system, Handle handle) {
return ResultInvalidHandle;
}
-static ResultCode ResetSignal32(Core::System& system, Handle handle) {
+static Result ResetSignal32(Core::System& system, Handle handle) {
return ResetSignal(system, handle);
}
@@ -2139,8 +2183,8 @@ constexpr bool IsValidTransferMemoryPermission(MemoryPermission perm) {
} // Anonymous namespace
/// Creates a TransferMemory object
-static ResultCode CreateTransferMemory(Core::System& system, Handle* out, VAddr address, u64 size,
- MemoryPermission map_perm) {
+static Result CreateTransferMemory(Core::System& system, Handle* out, VAddr address, u64 size,
+ MemoryPermission map_perm) {
auto& kernel = system.Kernel();
// Validate the size.
@@ -2186,13 +2230,13 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* out, VAddr
return ResultSuccess;
}
-static ResultCode CreateTransferMemory32(Core::System& system, Handle* out, u32 address, u32 size,
- MemoryPermission map_perm) {
+static Result CreateTransferMemory32(Core::System& system, Handle* out, u32 address, u32 size,
+ MemoryPermission map_perm) {
return CreateTransferMemory(system, out, address, size, map_perm);
}
-static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle, s32* out_core_id,
- u64* out_affinity_mask) {
+static Result GetThreadCoreMask(Core::System& system, Handle thread_handle, s32* out_core_id,
+ u64* out_affinity_mask) {
LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle);
// Get the thread from its handle.
@@ -2206,8 +2250,8 @@ static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle,
return ResultSuccess;
}
-static ResultCode GetThreadCoreMask32(Core::System& system, Handle thread_handle, s32* out_core_id,
- u32* out_affinity_mask_low, u32* out_affinity_mask_high) {
+static Result GetThreadCoreMask32(Core::System& system, Handle thread_handle, s32* out_core_id,
+ u32* out_affinity_mask_low, u32* out_affinity_mask_high) {
u64 out_affinity_mask{};
const auto result = GetThreadCoreMask(system, thread_handle, out_core_id, &out_affinity_mask);
*out_affinity_mask_high = static_cast<u32>(out_affinity_mask >> 32);
@@ -2215,8 +2259,8 @@ static ResultCode GetThreadCoreMask32(Core::System& system, Handle thread_handle
return result;
}
-static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle, s32 core_id,
- u64 affinity_mask) {
+static Result SetThreadCoreMask(Core::System& system, Handle thread_handle, s32 core_id,
+ u64 affinity_mask) {
// Determine the core id/affinity mask.
if (core_id == IdealCoreUseProcessValue) {
core_id = system.Kernel().CurrentProcess()->GetIdealCoreId();
@@ -2247,13 +2291,13 @@ static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle,
return ResultSuccess;
}
-static ResultCode SetThreadCoreMask32(Core::System& system, Handle thread_handle, s32 core_id,
- u32 affinity_mask_low, u32 affinity_mask_high) {
+static Result SetThreadCoreMask32(Core::System& system, Handle thread_handle, s32 core_id,
+ u32 affinity_mask_low, u32 affinity_mask_high) {
const auto affinity_mask = u64{affinity_mask_low} | (u64{affinity_mask_high} << 32);
return SetThreadCoreMask(system, thread_handle, core_id, affinity_mask);
}
-static ResultCode SignalEvent(Core::System& system, Handle event_handle) {
+static Result SignalEvent(Core::System& system, Handle event_handle) {
LOG_DEBUG(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle);
// Get the current handle table.
@@ -2266,11 +2310,11 @@ static ResultCode SignalEvent(Core::System& system, Handle event_handle) {
return writable_event->Signal();
}
-static ResultCode SignalEvent32(Core::System& system, Handle event_handle) {
+static Result SignalEvent32(Core::System& system, Handle event_handle) {
return SignalEvent(system, event_handle);
}
-static ResultCode ClearEvent(Core::System& system, Handle event_handle) {
+static Result ClearEvent(Core::System& system, Handle event_handle) {
LOG_TRACE(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle);
// Get the current handle table.
@@ -2297,11 +2341,11 @@ static ResultCode ClearEvent(Core::System& system, Handle event_handle) {
return ResultInvalidHandle;
}
-static ResultCode ClearEvent32(Core::System& system, Handle event_handle) {
+static Result ClearEvent32(Core::System& system, Handle event_handle) {
return ClearEvent(system, event_handle);
}
-static ResultCode CreateEvent(Core::System& system, Handle* out_write, Handle* out_read) {
+static Result CreateEvent(Core::System& system, Handle* out_write, Handle* out_read) {
LOG_DEBUG(Kernel_SVC, "called");
// Get the kernel reference and handle table.
@@ -2318,7 +2362,7 @@ static ResultCode CreateEvent(Core::System& system, Handle* out_write, Handle* o
R_UNLESS(event != nullptr, ResultOutOfResource);
// Initialize the event.
- event->Initialize("CreateEvent");
+ event->Initialize("CreateEvent", kernel.CurrentProcess());
// Commit the thread reservation.
event_reservation.Commit();
@@ -2346,11 +2390,11 @@ static ResultCode CreateEvent(Core::System& system, Handle* out_write, Handle* o
return ResultSuccess;
}
-static ResultCode CreateEvent32(Core::System& system, Handle* out_write, Handle* out_read) {
+static Result CreateEvent32(Core::System& system, Handle* out_write, Handle* out_read) {
return CreateEvent(system, out_write, out_read);
}
-static ResultCode GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) {
+static Result GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) {
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type);
// This function currently only allows retrieving a process' status.
@@ -2376,7 +2420,7 @@ static ResultCode GetProcessInfo(Core::System& system, u64* out, Handle process_
return ResultSuccess;
}
-static ResultCode CreateResourceLimit(Core::System& system, Handle* out_handle) {
+static Result CreateResourceLimit(Core::System& system, Handle* out_handle) {
LOG_DEBUG(Kernel_SVC, "called");
// Create a new resource limit.
@@ -2399,9 +2443,8 @@ static ResultCode CreateResourceLimit(Core::System& system, Handle* out_handle)
return ResultSuccess;
}
-static ResultCode GetResourceLimitLimitValue(Core::System& system, u64* out_limit_value,
- Handle resource_limit_handle,
- LimitableResource which) {
+static Result GetResourceLimitLimitValue(Core::System& system, u64* out_limit_value,
+ Handle resource_limit_handle, LimitableResource which) {
LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}", resource_limit_handle,
which);
@@ -2420,9 +2463,8 @@ static ResultCode GetResourceLimitLimitValue(Core::System& system, u64* out_limi
return ResultSuccess;
}
-static ResultCode GetResourceLimitCurrentValue(Core::System& system, u64* out_current_value,
- Handle resource_limit_handle,
- LimitableResource which) {
+static Result GetResourceLimitCurrentValue(Core::System& system, u64* out_current_value,
+ Handle resource_limit_handle, LimitableResource which) {
LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}", resource_limit_handle,
which);
@@ -2441,8 +2483,8 @@ static ResultCode GetResourceLimitCurrentValue(Core::System& system, u64* out_cu
return ResultSuccess;
}
-static ResultCode SetResourceLimitLimitValue(Core::System& system, Handle resource_limit_handle,
- LimitableResource which, u64 limit_value) {
+static Result SetResourceLimitLimitValue(Core::System& system, Handle resource_limit_handle,
+ LimitableResource which, u64 limit_value) {
LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}, limit_value={}",
resource_limit_handle, which, limit_value);
@@ -2461,8 +2503,8 @@ static ResultCode SetResourceLimitLimitValue(Core::System& system, Handle resour
return ResultSuccess;
}
-static ResultCode GetProcessList(Core::System& system, u32* out_num_processes,
- VAddr out_process_ids, u32 out_process_ids_size) {
+static Result GetProcessList(Core::System& system, u32* out_num_processes, VAddr out_process_ids,
+ u32 out_process_ids_size) {
LOG_DEBUG(Kernel_SVC, "called. out_process_ids=0x{:016X}, out_process_ids_size={}",
out_process_ids, out_process_ids_size);
@@ -2498,8 +2540,8 @@ static ResultCode GetProcessList(Core::System& system, u32* out_num_processes,
return ResultSuccess;
}
-static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAddr out_thread_ids,
- u32 out_thread_ids_size, Handle debug_handle) {
+static Result GetThreadList(Core::System& system, u32* out_num_threads, VAddr out_thread_ids,
+ u32 out_thread_ids_size, Handle debug_handle) {
// TODO: Handle this case when debug events are supported.
UNIMPLEMENTED_IF(debug_handle != InvalidHandle);
@@ -2513,7 +2555,7 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd
return ResultOutOfRange;
}
- const auto* const current_process = system.Kernel().CurrentProcess();
+ auto* const current_process = system.Kernel().CurrentProcess();
const auto total_copy_size = out_thread_ids_size * sizeof(u64);
if (out_thread_ids_size > 0 &&
@@ -2538,9 +2580,9 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd
return ResultSuccess;
}
-static ResultCode FlushProcessDataCache32([[maybe_unused]] Core::System& system,
- [[maybe_unused]] Handle handle,
- [[maybe_unused]] u32 address, [[maybe_unused]] u32 size) {
+static Result FlushProcessDataCache32([[maybe_unused]] Core::System& system,
+ [[maybe_unused]] Handle handle, [[maybe_unused]] u32 address,
+ [[maybe_unused]] u32 size) {
// Note(Blinkhawk): For emulation purposes of the data cache this is mostly a no-op,
// as all emulation is done in the same cache level in host architecture, thus data cache
// does not need flushing.
@@ -2559,9 +2601,9 @@ struct FunctionDef {
} // namespace
static const FunctionDef SVC_Table_32[] = {
- {0x00, nullptr, "Unknown"},
+ {0x00, nullptr, "Unknown0"},
{0x01, SvcWrap32<SetHeapSize32>, "SetHeapSize32"},
- {0x02, nullptr, "Unknown"},
+ {0x02, nullptr, "SetMemoryPermission32"},
{0x03, SvcWrap32<SetMemoryAttribute32>, "SetMemoryAttribute32"},
{0x04, SvcWrap32<MapMemory32>, "MapMemory32"},
{0x05, SvcWrap32<UnmapMemory32>, "UnmapMemory32"},
@@ -2591,97 +2633,97 @@ static const FunctionDef SVC_Table_32[] = {
{0x1d, SvcWrap32<SignalProcessWideKey32>, "SignalProcessWideKey32"},
{0x1e, SvcWrap32<GetSystemTick32>, "GetSystemTick32"},
{0x1f, SvcWrap32<ConnectToNamedPort32>, "ConnectToNamedPort32"},
- {0x20, nullptr, "Unknown"},
+ {0x20, nullptr, "SendSyncRequestLight32"},
{0x21, SvcWrap32<SendSyncRequest32>, "SendSyncRequest32"},
{0x22, nullptr, "SendSyncRequestWithUserBuffer32"},
- {0x23, nullptr, "Unknown"},
+ {0x23, nullptr, "SendAsyncRequestWithUserBuffer32"},
{0x24, SvcWrap32<GetProcessId32>, "GetProcessId32"},
{0x25, SvcWrap32<GetThreadId32>, "GetThreadId32"},
{0x26, SvcWrap32<Break32>, "Break32"},
- {0x27, nullptr, "OutputDebugString32"},
- {0x28, nullptr, "Unknown"},
+ {0x27, SvcWrap32<OutputDebugString32>, "OutputDebugString32"},
+ {0x28, nullptr, "ReturnFromException32"},
{0x29, SvcWrap32<GetInfo32>, "GetInfo32"},
- {0x2a, nullptr, "Unknown"},
- {0x2b, nullptr, "Unknown"},
+ {0x2a, nullptr, "FlushEntireDataCache32"},
+ {0x2b, nullptr, "FlushDataCache32"},
{0x2c, SvcWrap32<MapPhysicalMemory32>, "MapPhysicalMemory32"},
{0x2d, SvcWrap32<UnmapPhysicalMemory32>, "UnmapPhysicalMemory32"},
- {0x2e, nullptr, "Unknown"},
- {0x2f, nullptr, "Unknown"},
- {0x30, nullptr, "Unknown"},
- {0x31, nullptr, "Unknown"},
+ {0x2e, nullptr, "GetDebugFutureThreadInfo32"},
+ {0x2f, nullptr, "GetLastThreadInfo32"},
+ {0x30, nullptr, "GetResourceLimitLimitValue32"},
+ {0x31, nullptr, "GetResourceLimitCurrentValue32"},
{0x32, SvcWrap32<SetThreadActivity32>, "SetThreadActivity32"},
{0x33, SvcWrap32<GetThreadContext32>, "GetThreadContext32"},
{0x34, SvcWrap32<WaitForAddress32>, "WaitForAddress32"},
{0x35, SvcWrap32<SignalToAddress32>, "SignalToAddress32"},
{0x36, SvcWrap32<SynchronizePreemptionState>, "SynchronizePreemptionState32"},
- {0x37, nullptr, "Unknown"},
- {0x38, nullptr, "Unknown"},
- {0x39, nullptr, "Unknown"},
- {0x3a, nullptr, "Unknown"},
- {0x3b, nullptr, "Unknown"},
- {0x3c, nullptr, "Unknown"},
- {0x3d, nullptr, "Unknown"},
- {0x3e, nullptr, "Unknown"},
- {0x3f, nullptr, "Unknown"},
+ {0x37, nullptr, "GetResourceLimitPeakValue32"},
+ {0x38, nullptr, "Unknown38"},
+ {0x39, nullptr, "CreateIoPool32"},
+ {0x3a, nullptr, "CreateIoRegion32"},
+ {0x3b, nullptr, "Unknown3b"},
+ {0x3c, nullptr, "KernelDebug32"},
+ {0x3d, nullptr, "ChangeKernelTraceState32"},
+ {0x3e, nullptr, "Unknown3e"},
+ {0x3f, nullptr, "Unknown3f"},
{0x40, nullptr, "CreateSession32"},
{0x41, nullptr, "AcceptSession32"},
- {0x42, nullptr, "Unknown"},
+ {0x42, nullptr, "ReplyAndReceiveLight32"},
{0x43, nullptr, "ReplyAndReceive32"},
- {0x44, nullptr, "Unknown"},
+ {0x44, nullptr, "ReplyAndReceiveWithUserBuffer32"},
{0x45, SvcWrap32<CreateEvent32>, "CreateEvent32"},
- {0x46, nullptr, "Unknown"},
- {0x47, nullptr, "Unknown"},
- {0x48, nullptr, "Unknown"},
- {0x49, nullptr, "Unknown"},
- {0x4a, nullptr, "Unknown"},
- {0x4b, nullptr, "Unknown"},
- {0x4c, nullptr, "Unknown"},
- {0x4d, nullptr, "Unknown"},
- {0x4e, nullptr, "Unknown"},
- {0x4f, nullptr, "Unknown"},
- {0x50, nullptr, "Unknown"},
- {0x51, nullptr, "Unknown"},
- {0x52, nullptr, "Unknown"},
- {0x53, nullptr, "Unknown"},
- {0x54, nullptr, "Unknown"},
- {0x55, nullptr, "Unknown"},
- {0x56, nullptr, "Unknown"},
- {0x57, nullptr, "Unknown"},
- {0x58, nullptr, "Unknown"},
- {0x59, nullptr, "Unknown"},
- {0x5a, nullptr, "Unknown"},
- {0x5b, nullptr, "Unknown"},
- {0x5c, nullptr, "Unknown"},
- {0x5d, nullptr, "Unknown"},
- {0x5e, nullptr, "Unknown"},
+ {0x46, nullptr, "MapIoRegion32"},
+ {0x47, nullptr, "UnmapIoRegion32"},
+ {0x48, nullptr, "MapPhysicalMemoryUnsafe32"},
+ {0x49, nullptr, "UnmapPhysicalMemoryUnsafe32"},
+ {0x4a, nullptr, "SetUnsafeLimit32"},
+ {0x4b, SvcWrap32<CreateCodeMemory32>, "CreateCodeMemory32"},
+ {0x4c, SvcWrap32<ControlCodeMemory32>, "ControlCodeMemory32"},
+ {0x4d, nullptr, "SleepSystem32"},
+ {0x4e, nullptr, "ReadWriteRegister32"},
+ {0x4f, nullptr, "SetProcessActivity32"},
+ {0x50, nullptr, "CreateSharedMemory32"},
+ {0x51, nullptr, "MapTransferMemory32"},
+ {0x52, nullptr, "UnmapTransferMemory32"},
+ {0x53, nullptr, "CreateInterruptEvent32"},
+ {0x54, nullptr, "QueryPhysicalAddress32"},
+ {0x55, nullptr, "QueryIoMapping32"},
+ {0x56, nullptr, "CreateDeviceAddressSpace32"},
+ {0x57, nullptr, "AttachDeviceAddressSpace32"},
+ {0x58, nullptr, "DetachDeviceAddressSpace32"},
+ {0x59, nullptr, "MapDeviceAddressSpaceByForce32"},
+ {0x5a, nullptr, "MapDeviceAddressSpaceAligned32"},
+ {0x5b, nullptr, "MapDeviceAddressSpace32"},
+ {0x5c, nullptr, "UnmapDeviceAddressSpace32"},
+ {0x5d, nullptr, "InvalidateProcessDataCache32"},
+ {0x5e, nullptr, "StoreProcessDataCache32"},
{0x5F, SvcWrap32<FlushProcessDataCache32>, "FlushProcessDataCache32"},
- {0x60, nullptr, "Unknown"},
- {0x61, nullptr, "Unknown"},
- {0x62, nullptr, "Unknown"},
- {0x63, nullptr, "Unknown"},
- {0x64, nullptr, "Unknown"},
+ {0x60, nullptr, "StoreProcessDataCache32"},
+ {0x61, nullptr, "BreakDebugProcess32"},
+ {0x62, nullptr, "TerminateDebugProcess32"},
+ {0x63, nullptr, "GetDebugEvent32"},
+ {0x64, nullptr, "ContinueDebugEvent32"},
{0x65, nullptr, "GetProcessList32"},
- {0x66, nullptr, "Unknown"},
- {0x67, nullptr, "Unknown"},
- {0x68, nullptr, "Unknown"},
- {0x69, nullptr, "Unknown"},
- {0x6A, nullptr, "Unknown"},
- {0x6B, nullptr, "Unknown"},
- {0x6C, nullptr, "Unknown"},
- {0x6D, nullptr, "Unknown"},
- {0x6E, nullptr, "Unknown"},
+ {0x66, nullptr, "GetThreadList"},
+ {0x67, nullptr, "GetDebugThreadContext32"},
+ {0x68, nullptr, "SetDebugThreadContext32"},
+ {0x69, nullptr, "QueryDebugProcessMemory32"},
+ {0x6A, nullptr, "ReadDebugProcessMemory32"},
+ {0x6B, nullptr, "WriteDebugProcessMemory32"},
+ {0x6C, nullptr, "SetHardwareBreakPoint32"},
+ {0x6D, nullptr, "GetDebugThreadParam32"},
+ {0x6E, nullptr, "Unknown6E"},
{0x6f, nullptr, "GetSystemInfo32"},
{0x70, nullptr, "CreatePort32"},
{0x71, nullptr, "ManageNamedPort32"},
{0x72, nullptr, "ConnectToPort32"},
{0x73, nullptr, "SetProcessMemoryPermission32"},
- {0x74, nullptr, "Unknown"},
- {0x75, nullptr, "Unknown"},
- {0x76, nullptr, "Unknown"},
+ {0x74, nullptr, "MapProcessMemory32"},
+ {0x75, nullptr, "UnmapProcessMemory32"},
+ {0x76, nullptr, "QueryProcessMemory32"},
{0x77, nullptr, "MapProcessCodeMemory32"},
{0x78, nullptr, "UnmapProcessCodeMemory32"},
- {0x79, nullptr, "Unknown"},
- {0x7A, nullptr, "Unknown"},
+ {0x79, nullptr, "CreateProcess32"},
+ {0x7A, nullptr, "StartProcess32"},
{0x7B, nullptr, "TerminateProcess32"},
{0x7C, nullptr, "GetProcessInfo32"},
{0x7D, nullptr, "CreateResourceLimit32"},
@@ -2754,7 +2796,7 @@ static const FunctionDef SVC_Table_32[] = {
};
static const FunctionDef SVC_Table_64[] = {
- {0x00, nullptr, "Unknown"},
+ {0x00, nullptr, "Unknown0"},
{0x01, SvcWrap64<SetHeapSize>, "SetHeapSize"},
{0x02, SvcWrap64<SetMemoryPermission>, "SetMemoryPermission"},
{0x03, SvcWrap64<SetMemoryAttribute>, "SetMemoryAttribute"},
@@ -2809,23 +2851,23 @@ static const FunctionDef SVC_Table_64[] = {
{0x34, SvcWrap64<WaitForAddress>, "WaitForAddress"},
{0x35, SvcWrap64<SignalToAddress>, "SignalToAddress"},
{0x36, SvcWrap64<SynchronizePreemptionState>, "SynchronizePreemptionState"},
- {0x37, nullptr, "Unknown"},
- {0x38, nullptr, "Unknown"},
- {0x39, nullptr, "Unknown"},
- {0x3A, nullptr, "Unknown"},
- {0x3B, nullptr, "Unknown"},
+ {0x37, nullptr, "GetResourceLimitPeakValue"},
+ {0x38, nullptr, "Unknown38"},
+ {0x39, nullptr, "CreateIoPool"},
+ {0x3A, nullptr, "CreateIoRegion"},
+ {0x3B, nullptr, "Unknown3B"},
{0x3C, SvcWrap64<KernelDebug>, "KernelDebug"},
{0x3D, SvcWrap64<ChangeKernelTraceState>, "ChangeKernelTraceState"},
- {0x3E, nullptr, "Unknown"},
- {0x3F, nullptr, "Unknown"},
+ {0x3E, nullptr, "Unknown3e"},
+ {0x3F, nullptr, "Unknown3f"},
{0x40, nullptr, "CreateSession"},
{0x41, nullptr, "AcceptSession"},
{0x42, nullptr, "ReplyAndReceiveLight"},
{0x43, nullptr, "ReplyAndReceive"},
{0x44, nullptr, "ReplyAndReceiveWithUserBuffer"},
{0x45, SvcWrap64<CreateEvent>, "CreateEvent"},
- {0x46, nullptr, "Unknown"},
- {0x47, nullptr, "Unknown"},
+ {0x46, nullptr, "MapIoRegion"},
+ {0x47, nullptr, "UnmapIoRegion"},
{0x48, nullptr, "MapPhysicalMemoryUnsafe"},
{0x49, nullptr, "UnmapPhysicalMemoryUnsafe"},
{0x4A, nullptr, "SetUnsafeLimit"},
@@ -2864,7 +2906,7 @@ static const FunctionDef SVC_Table_64[] = {
{0x6B, nullptr, "WriteDebugProcessMemory"},
{0x6C, nullptr, "SetHardwareBreakPoint"},
{0x6D, nullptr, "GetDebugThreadParam"},
- {0x6E, nullptr, "Unknown"},
+ {0x6E, nullptr, "Unknown6E"},
{0x6F, nullptr, "GetSystemInfo"},
{0x70, nullptr, "CreatePort"},
{0x71, nullptr, "ManageNamedPort"},
@@ -2965,11 +3007,10 @@ static const FunctionDef* GetSVCInfo64(u32 func_num) {
}
void Call(Core::System& system, u32 immediate) {
- system.ExitDynarmicProfile();
auto& kernel = system.Kernel();
kernel.EnterSVCProfile();
- auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
+ auto* thread = GetCurrentThreadPointer(kernel);
thread->SetIsCallingSvc();
const FunctionDef* info = system.CurrentProcess()->Is64BitProcess() ? GetSVCInfo64(immediate)
@@ -2985,13 +3026,6 @@ void Call(Core::System& system, u32 immediate) {
}
kernel.ExitSVCProfile();
-
- if (!thread->IsCallingSvc()) {
- auto* host_context = thread->GetHostContext().get();
- host_context->Rewind();
- }
-
- system.EnterDynarmicProfile();
}
} // namespace Kernel::Svc
diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h
index 46e64277e..13f061b83 100644
--- a/src/core/hle/kernel/svc.h
+++ b/src/core/hle/kernel/svc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/svc_common.h b/src/core/hle/kernel/svc_common.h
index 25de6e437..95750c3eb 100644
--- a/src/core/hle/kernel/svc_common.h
+++ b/src/core/hle/kernel/svc_common.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/kernel/svc_results.h b/src/core/hle/kernel/svc_results.h
index 53a940723..f27cade33 100644
--- a/src/core/hle/kernel/svc_results.h
+++ b/src/core/hle/kernel/svc_results.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,34 +9,34 @@ namespace Kernel {
// Confirmed Switch kernel error codes
-constexpr ResultCode ResultOutOfSessions{ErrorModule::Kernel, 7};
-constexpr ResultCode ResultInvalidArgument{ErrorModule::Kernel, 14};
-constexpr ResultCode ResultNoSynchronizationObject{ErrorModule::Kernel, 57};
-constexpr ResultCode ResultTerminationRequested{ErrorModule::Kernel, 59};
-constexpr ResultCode ResultInvalidSize{ErrorModule::Kernel, 101};
-constexpr ResultCode ResultInvalidAddress{ErrorModule::Kernel, 102};
-constexpr ResultCode ResultOutOfResource{ErrorModule::Kernel, 103};
-constexpr ResultCode ResultOutOfMemory{ErrorModule::Kernel, 104};
-constexpr ResultCode ResultOutOfHandles{ErrorModule::Kernel, 105};
-constexpr ResultCode ResultInvalidCurrentMemory{ErrorModule::Kernel, 106};
-constexpr ResultCode ResultInvalidNewMemoryPermission{ErrorModule::Kernel, 108};
-constexpr ResultCode ResultInvalidMemoryRegion{ErrorModule::Kernel, 110};
-constexpr ResultCode ResultInvalidPriority{ErrorModule::Kernel, 112};
-constexpr ResultCode ResultInvalidCoreId{ErrorModule::Kernel, 113};
-constexpr ResultCode ResultInvalidHandle{ErrorModule::Kernel, 114};
-constexpr ResultCode ResultInvalidPointer{ErrorModule::Kernel, 115};
-constexpr ResultCode ResultInvalidCombination{ErrorModule::Kernel, 116};
-constexpr ResultCode ResultTimedOut{ErrorModule::Kernel, 117};
-constexpr ResultCode ResultCancelled{ErrorModule::Kernel, 118};
-constexpr ResultCode ResultOutOfRange{ErrorModule::Kernel, 119};
-constexpr ResultCode ResultInvalidEnumValue{ErrorModule::Kernel, 120};
-constexpr ResultCode ResultNotFound{ErrorModule::Kernel, 121};
-constexpr ResultCode ResultBusy{ErrorModule::Kernel, 122};
-constexpr ResultCode ResultSessionClosed{ErrorModule::Kernel, 123};
-constexpr ResultCode ResultInvalidState{ErrorModule::Kernel, 125};
-constexpr ResultCode ResultReservedUsed{ErrorModule::Kernel, 126};
-constexpr ResultCode ResultPortClosed{ErrorModule::Kernel, 131};
-constexpr ResultCode ResultLimitReached{ErrorModule::Kernel, 132};
-constexpr ResultCode ResultInvalidId{ErrorModule::Kernel, 519};
+constexpr Result ResultOutOfSessions{ErrorModule::Kernel, 7};
+constexpr Result ResultInvalidArgument{ErrorModule::Kernel, 14};
+constexpr Result ResultNoSynchronizationObject{ErrorModule::Kernel, 57};
+constexpr Result ResultTerminationRequested{ErrorModule::Kernel, 59};
+constexpr Result ResultInvalidSize{ErrorModule::Kernel, 101};
+constexpr Result ResultInvalidAddress{ErrorModule::Kernel, 102};
+constexpr Result ResultOutOfResource{ErrorModule::Kernel, 103};
+constexpr Result ResultOutOfMemory{ErrorModule::Kernel, 104};
+constexpr Result ResultOutOfHandles{ErrorModule::Kernel, 105};
+constexpr Result ResultInvalidCurrentMemory{ErrorModule::Kernel, 106};
+constexpr Result ResultInvalidNewMemoryPermission{ErrorModule::Kernel, 108};
+constexpr Result ResultInvalidMemoryRegion{ErrorModule::Kernel, 110};
+constexpr Result ResultInvalidPriority{ErrorModule::Kernel, 112};
+constexpr Result ResultInvalidCoreId{ErrorModule::Kernel, 113};
+constexpr Result ResultInvalidHandle{ErrorModule::Kernel, 114};
+constexpr Result ResultInvalidPointer{ErrorModule::Kernel, 115};
+constexpr Result ResultInvalidCombination{ErrorModule::Kernel, 116};
+constexpr Result ResultTimedOut{ErrorModule::Kernel, 117};
+constexpr Result ResultCancelled{ErrorModule::Kernel, 118};
+constexpr Result ResultOutOfRange{ErrorModule::Kernel, 119};
+constexpr Result ResultInvalidEnumValue{ErrorModule::Kernel, 120};
+constexpr Result ResultNotFound{ErrorModule::Kernel, 121};
+constexpr Result ResultBusy{ErrorModule::Kernel, 122};
+constexpr Result ResultSessionClosed{ErrorModule::Kernel, 123};
+constexpr Result ResultInvalidState{ErrorModule::Kernel, 125};
+constexpr Result ResultReservedUsed{ErrorModule::Kernel, 126};
+constexpr Result ResultPortClosed{ErrorModule::Kernel, 131};
+constexpr Result ResultLimitReached{ErrorModule::Kernel, 132};
+constexpr Result ResultInvalidId{ErrorModule::Kernel, 519};
} // namespace Kernel
diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h
index 365e22e4e..79e15183a 100644
--- a/src/core/hle/kernel/svc_types.h
+++ b/src/core/hle/kernel/svc_types.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -96,4 +95,6 @@ constexpr inline s32 IdealCoreNoUpdate = -3;
constexpr inline s32 LowestThreadPriority = 63;
constexpr inline s32 HighestThreadPriority = 0;
+constexpr inline size_t ThreadLocalRegionSize = 0x200;
+
} // namespace Kernel::Svc
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index a60adfcab..4bc49087e 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -34,24 +33,24 @@ static inline void FuncReturn32(Core::System& system, u32 result) {
}
////////////////////////////////////////////////////////////////////////////////////////////////////
-// Function wrappers that return type ResultCode
+// Function wrappers that return type Result
-template <ResultCode func(Core::System&, u64)>
+template <Result func(Core::System&, u64)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, Param(system, 0)).raw);
}
-template <ResultCode func(Core::System&, u64, u64)>
+template <Result func(Core::System&, u64, u64)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, Param(system, 0), Param(system, 1)).raw);
}
-template <ResultCode func(Core::System&, u32)>
+template <Result func(Core::System&, u32)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw);
}
-template <ResultCode func(Core::System&, u32, u32)>
+template <Result func(Core::System&, u32, u32)>
void SvcWrap64(Core::System& system) {
FuncReturn(
system,
@@ -59,14 +58,14 @@ void SvcWrap64(Core::System& system) {
}
// Used by SetThreadActivity
-template <ResultCode func(Core::System&, Handle, Svc::ThreadActivity)>
+template <Result func(Core::System&, Handle, Svc::ThreadActivity)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)),
static_cast<Svc::ThreadActivity>(Param(system, 1)))
.raw);
}
-template <ResultCode func(Core::System&, u32, u64, u64, u64)>
+template <Result func(Core::System&, u32, u64, u64, u64)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1),
Param(system, 2), Param(system, 3))
@@ -74,7 +73,7 @@ void SvcWrap64(Core::System& system) {
}
// Used by MapProcessMemory and UnmapProcessMemory
-template <ResultCode func(Core::System&, u64, u32, u64, u64)>
+template <Result func(Core::System&, u64, u32, u64, u64)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, Param(system, 0), static_cast<u32>(Param(system, 1)),
Param(system, 2), Param(system, 3))
@@ -82,7 +81,7 @@ void SvcWrap64(Core::System& system) {
}
// Used by ControlCodeMemory
-template <ResultCode func(Core::System&, Handle, u32, u64, u64, Svc::MemoryPermission)>
+template <Result func(Core::System&, Handle, u32, u64, u64, Svc::MemoryPermission)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, static_cast<Handle>(Param(system, 0)),
static_cast<u32>(Param(system, 1)), Param(system, 2), Param(system, 3),
@@ -90,7 +89,7 @@ void SvcWrap64(Core::System& system) {
.raw);
}
-template <ResultCode func(Core::System&, u32*)>
+template <Result func(Core::System&, u32*)>
void SvcWrap64(Core::System& system) {
u32 param = 0;
const u32 retval = func(system, &param).raw;
@@ -98,7 +97,7 @@ void SvcWrap64(Core::System& system) {
FuncReturn(system, retval);
}
-template <ResultCode func(Core::System&, u32*, u32)>
+template <Result func(Core::System&, u32*, u32)>
void SvcWrap64(Core::System& system) {
u32 param_1 = 0;
const u32 retval = func(system, &param_1, static_cast<u32>(Param(system, 1))).raw;
@@ -106,7 +105,7 @@ void SvcWrap64(Core::System& system) {
FuncReturn(system, retval);
}
-template <ResultCode func(Core::System&, u32*, u32*)>
+template <Result func(Core::System&, u32*, u32*)>
void SvcWrap64(Core::System& system) {
u32 param_1 = 0;
u32 param_2 = 0;
@@ -119,7 +118,7 @@ void SvcWrap64(Core::System& system) {
FuncReturn(system, retval);
}
-template <ResultCode func(Core::System&, u32*, u64)>
+template <Result func(Core::System&, u32*, u64)>
void SvcWrap64(Core::System& system) {
u32 param_1 = 0;
const u32 retval = func(system, &param_1, Param(system, 1)).raw;
@@ -127,7 +126,7 @@ void SvcWrap64(Core::System& system) {
FuncReturn(system, retval);
}
-template <ResultCode func(Core::System&, u32*, u64, u32)>
+template <Result func(Core::System&, u32*, u64, u32)>
void SvcWrap64(Core::System& system) {
u32 param_1 = 0;
const u32 retval =
@@ -137,7 +136,7 @@ void SvcWrap64(Core::System& system) {
FuncReturn(system, retval);
}
-template <ResultCode func(Core::System&, u64*, u32)>
+template <Result func(Core::System&, u64*, u32)>
void SvcWrap64(Core::System& system) {
u64 param_1 = 0;
const u32 retval = func(system, &param_1, static_cast<u32>(Param(system, 1))).raw;
@@ -146,12 +145,12 @@ void SvcWrap64(Core::System& system) {
FuncReturn(system, retval);
}
-template <ResultCode func(Core::System&, u64, u32)>
+template <Result func(Core::System&, u64, u32)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, Param(system, 0), static_cast<u32>(Param(system, 1))).raw);
}
-template <ResultCode func(Core::System&, u64*, u64)>
+template <Result func(Core::System&, u64*, u64)>
void SvcWrap64(Core::System& system) {
u64 param_1 = 0;
const u32 retval = func(system, &param_1, Param(system, 1)).raw;
@@ -160,7 +159,7 @@ void SvcWrap64(Core::System& system) {
FuncReturn(system, retval);
}
-template <ResultCode func(Core::System&, u64*, u32, u32)>
+template <Result func(Core::System&, u64*, u32, u32)>
void SvcWrap64(Core::System& system) {
u64 param_1 = 0;
const u32 retval = func(system, &param_1, static_cast<u32>(Param(system, 1)),
@@ -172,7 +171,7 @@ void SvcWrap64(Core::System& system) {
}
// Used by GetResourceLimitLimitValue.
-template <ResultCode func(Core::System&, u64*, Handle, LimitableResource)>
+template <Result func(Core::System&, u64*, Handle, LimitableResource)>
void SvcWrap64(Core::System& system) {
u64 param_1 = 0;
const u32 retval = func(system, &param_1, static_cast<Handle>(Param(system, 1)),
@@ -183,13 +182,13 @@ void SvcWrap64(Core::System& system) {
FuncReturn(system, retval);
}
-template <ResultCode func(Core::System&, u32, u64)>
+template <Result func(Core::System&, u32, u64)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1)).raw);
}
// Used by SetResourceLimitLimitValue
-template <ResultCode func(Core::System&, Handle, LimitableResource, u64)>
+template <Result func(Core::System&, Handle, LimitableResource, u64)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, static_cast<Handle>(Param(system, 0)),
static_cast<LimitableResource>(Param(system, 1)), Param(system, 2))
@@ -197,7 +196,7 @@ void SvcWrap64(Core::System& system) {
}
// Used by SetThreadCoreMask
-template <ResultCode func(Core::System&, Handle, s32, u64)>
+template <Result func(Core::System&, Handle, s32, u64)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)),
static_cast<s32>(Param(system, 1)), Param(system, 2))
@@ -205,44 +204,44 @@ void SvcWrap64(Core::System& system) {
}
// Used by GetThreadCoreMask
-template <ResultCode func(Core::System&, Handle, s32*, u64*)>
+template <Result func(Core::System&, Handle, s32*, u64*)>
void SvcWrap64(Core::System& system) {
s32 param_1 = 0;
u64 param_2 = 0;
- const ResultCode retval = func(system, static_cast<u32>(Param(system, 2)), &param_1, &param_2);
+ const Result retval = func(system, static_cast<u32>(Param(system, 2)), &param_1, &param_2);
system.CurrentArmInterface().SetReg(1, param_1);
system.CurrentArmInterface().SetReg(2, param_2);
FuncReturn(system, retval.raw);
}
-template <ResultCode func(Core::System&, u64, u64, u32, u32)>
+template <Result func(Core::System&, u64, u64, u32, u32)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, Param(system, 0), Param(system, 1),
static_cast<u32>(Param(system, 2)), static_cast<u32>(Param(system, 3)))
.raw);
}
-template <ResultCode func(Core::System&, u64, u64, u32, u64)>
+template <Result func(Core::System&, u64, u64, u32, u64)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, Param(system, 0), Param(system, 1),
static_cast<u32>(Param(system, 2)), Param(system, 3))
.raw);
}
-template <ResultCode func(Core::System&, u32, u64, u32)>
+template <Result func(Core::System&, u32, u64, u32)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1),
static_cast<u32>(Param(system, 2)))
.raw);
}
-template <ResultCode func(Core::System&, u64, u64, u64)>
+template <Result func(Core::System&, u64, u64, u64)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, Param(system, 0), Param(system, 1), Param(system, 2)).raw);
}
-template <ResultCode func(Core::System&, u64, u64, u32)>
+template <Result func(Core::System&, u64, u64, u32)>
void SvcWrap64(Core::System& system) {
FuncReturn(
system,
@@ -250,7 +249,7 @@ void SvcWrap64(Core::System& system) {
}
// Used by SetMemoryPermission
-template <ResultCode func(Core::System&, u64, u64, Svc::MemoryPermission)>
+template <Result func(Core::System&, u64, u64, Svc::MemoryPermission)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, Param(system, 0), Param(system, 1),
static_cast<Svc::MemoryPermission>(Param(system, 2)))
@@ -258,14 +257,14 @@ void SvcWrap64(Core::System& system) {
}
// Used by MapSharedMemory
-template <ResultCode func(Core::System&, Handle, u64, u64, Svc::MemoryPermission)>
+template <Result func(Core::System&, Handle, u64, u64, Svc::MemoryPermission)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, static_cast<Handle>(Param(system, 0)), Param(system, 1),
Param(system, 2), static_cast<Svc::MemoryPermission>(Param(system, 3)))
.raw);
}
-template <ResultCode func(Core::System&, u32, u64, u64)>
+template <Result func(Core::System&, u32, u64, u64)>
void SvcWrap64(Core::System& system) {
FuncReturn(
system,
@@ -273,7 +272,7 @@ void SvcWrap64(Core::System& system) {
}
// Used by WaitSynchronization
-template <ResultCode func(Core::System&, s32*, u64, s32, s64)>
+template <Result func(Core::System&, s32*, u64, s32, s64)>
void SvcWrap64(Core::System& system) {
s32 param_1 = 0;
const u32 retval = func(system, &param_1, Param(system, 1), static_cast<s32>(Param(system, 2)),
@@ -284,7 +283,7 @@ void SvcWrap64(Core::System& system) {
FuncReturn(system, retval);
}
-template <ResultCode func(Core::System&, u64, u64, u32, s64)>
+template <Result func(Core::System&, u64, u64, u32, s64)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, Param(system, 0), Param(system, 1),
static_cast<u32>(Param(system, 2)), static_cast<s64>(Param(system, 3)))
@@ -292,7 +291,7 @@ void SvcWrap64(Core::System& system) {
}
// Used by GetInfo
-template <ResultCode func(Core::System&, u64*, u64, Handle, u64)>
+template <Result func(Core::System&, u64*, u64, Handle, u64)>
void SvcWrap64(Core::System& system) {
u64 param_1 = 0;
const u32 retval = func(system, &param_1, Param(system, 1),
@@ -303,7 +302,7 @@ void SvcWrap64(Core::System& system) {
FuncReturn(system, retval);
}
-template <ResultCode func(Core::System&, u32*, u64, u64, u64, u32, s32)>
+template <Result func(Core::System&, u32*, u64, u64, u64, u32, s32)>
void SvcWrap64(Core::System& system) {
u32 param_1 = 0;
const u32 retval = func(system, &param_1, Param(system, 1), Param(system, 2), Param(system, 3),
@@ -315,7 +314,7 @@ void SvcWrap64(Core::System& system) {
}
// Used by CreateTransferMemory
-template <ResultCode func(Core::System&, Handle*, u64, u64, Svc::MemoryPermission)>
+template <Result func(Core::System&, Handle*, u64, u64, Svc::MemoryPermission)>
void SvcWrap64(Core::System& system) {
u32 param_1 = 0;
const u32 retval = func(system, &param_1, Param(system, 1), Param(system, 2),
@@ -327,7 +326,7 @@ void SvcWrap64(Core::System& system) {
}
// Used by CreateCodeMemory
-template <ResultCode func(Core::System&, Handle*, u64, u64)>
+template <Result func(Core::System&, Handle*, u64, u64)>
void SvcWrap64(Core::System& system) {
u32 param_1 = 0;
const u32 retval = func(system, &param_1, Param(system, 1), Param(system, 2)).raw;
@@ -336,7 +335,7 @@ void SvcWrap64(Core::System& system) {
FuncReturn(system, retval);
}
-template <ResultCode func(Core::System&, Handle*, u64, u32, u32)>
+template <Result func(Core::System&, Handle*, u64, u32, u32)>
void SvcWrap64(Core::System& system) {
u32 param_1 = 0;
const u32 retval = func(system, &param_1, Param(system, 1), static_cast<u32>(Param(system, 2)),
@@ -348,7 +347,7 @@ void SvcWrap64(Core::System& system) {
}
// Used by WaitForAddress
-template <ResultCode func(Core::System&, u64, Svc::ArbitrationType, s32, s64)>
+template <Result func(Core::System&, u64, Svc::ArbitrationType, s32, s64)>
void SvcWrap64(Core::System& system) {
FuncReturn(system,
func(system, Param(system, 0), static_cast<Svc::ArbitrationType>(Param(system, 1)),
@@ -357,7 +356,7 @@ void SvcWrap64(Core::System& system) {
}
// Used by SignalToAddress
-template <ResultCode func(Core::System&, u64, Svc::SignalType, s32, s32)>
+template <Result func(Core::System&, u64, Svc::SignalType, s32, s32)>
void SvcWrap64(Core::System& system) {
FuncReturn(system,
func(system, Param(system, 0), static_cast<Svc::SignalType>(Param(system, 1)),
@@ -426,7 +425,7 @@ void SvcWrap64(Core::System& system) {
}
// Used by QueryMemory32, ArbitrateLock32
-template <ResultCode func(Core::System&, u32, u32, u32)>
+template <Result func(Core::System&, u32, u32, u32)>
void SvcWrap32(Core::System& system) {
FuncReturn32(system,
func(system, Param32(system, 0), Param32(system, 1), Param32(system, 2)).raw);
@@ -457,7 +456,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by CreateThread32
-template <ResultCode func(Core::System&, Handle*, u32, u32, u32, u32, s32)>
+template <Result func(Core::System&, Handle*, u32, u32, u32, u32, s32)>
void SvcWrap32(Core::System& system) {
Handle param_1 = 0;
@@ -470,7 +469,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by GetInfo32
-template <ResultCode func(Core::System&, u32*, u32*, u32, u32, u32, u32)>
+template <Result func(Core::System&, u32*, u32*, u32, u32, u32, u32)>
void SvcWrap32(Core::System& system) {
u32 param_1 = 0;
u32 param_2 = 0;
@@ -485,7 +484,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by GetThreadPriority32, ConnectToNamedPort32
-template <ResultCode func(Core::System&, u32*, u32)>
+template <Result func(Core::System&, u32*, u32)>
void SvcWrap32(Core::System& system) {
u32 param_1 = 0;
const u32 retval = func(system, &param_1, Param32(system, 1)).raw;
@@ -494,7 +493,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by GetThreadId32
-template <ResultCode func(Core::System&, u32*, u32*, u32)>
+template <Result func(Core::System&, u32*, u32*, u32)>
void SvcWrap32(Core::System& system) {
u32 param_1 = 0;
u32 param_2 = 0;
@@ -517,7 +516,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by CreateEvent32
-template <ResultCode func(Core::System&, Handle*, Handle*)>
+template <Result func(Core::System&, Handle*, Handle*)>
void SvcWrap32(Core::System& system) {
Handle param_1 = 0;
Handle param_2 = 0;
@@ -529,7 +528,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by GetThreadId32
-template <ResultCode func(Core::System&, Handle, u32*, u32*, u32*)>
+template <Result func(Core::System&, Handle, u32*, u32*, u32*)>
void SvcWrap32(Core::System& system) {
u32 param_1 = 0;
u32 param_2 = 0;
@@ -543,7 +542,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by GetThreadCoreMask32
-template <ResultCode func(Core::System&, Handle, s32*, u32*, u32*)>
+template <Result func(Core::System&, Handle, s32*, u32*, u32*)>
void SvcWrap32(Core::System& system) {
s32 param_1 = 0;
u32 param_2 = 0;
@@ -563,7 +562,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by SetThreadActivity32
-template <ResultCode func(Core::System&, Handle, Svc::ThreadActivity)>
+template <Result func(Core::System&, Handle, Svc::ThreadActivity)>
void SvcWrap32(Core::System& system) {
const u32 retval = func(system, static_cast<Handle>(Param(system, 0)),
static_cast<Svc::ThreadActivity>(Param(system, 1)))
@@ -572,7 +571,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by SetThreadPriority32
-template <ResultCode func(Core::System&, Handle, u32)>
+template <Result func(Core::System&, Handle, u32)>
void SvcWrap32(Core::System& system) {
const u32 retval =
func(system, static_cast<Handle>(Param(system, 0)), static_cast<u32>(Param(system, 1))).raw;
@@ -580,7 +579,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by SetMemoryAttribute32
-template <ResultCode func(Core::System&, Handle, u32, u32, u32)>
+template <Result func(Core::System&, Handle, u32, u32, u32)>
void SvcWrap32(Core::System& system) {
const u32 retval =
func(system, static_cast<Handle>(Param(system, 0)), static_cast<u32>(Param(system, 1)),
@@ -590,7 +589,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by MapSharedMemory32
-template <ResultCode func(Core::System&, Handle, u32, u32, Svc::MemoryPermission)>
+template <Result func(Core::System&, Handle, u32, u32, Svc::MemoryPermission)>
void SvcWrap32(Core::System& system) {
const u32 retval = func(system, static_cast<Handle>(Param(system, 0)),
static_cast<u32>(Param(system, 1)), static_cast<u32>(Param(system, 2)),
@@ -600,7 +599,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by SetThreadCoreMask32
-template <ResultCode func(Core::System&, Handle, s32, u32, u32)>
+template <Result func(Core::System&, Handle, s32, u32, u32)>
void SvcWrap32(Core::System& system) {
const u32 retval =
func(system, static_cast<Handle>(Param(system, 0)), static_cast<s32>(Param(system, 1)),
@@ -610,7 +609,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by WaitProcessWideKeyAtomic32
-template <ResultCode func(Core::System&, u32, u32, Handle, u32, u32)>
+template <Result func(Core::System&, u32, u32, Handle, u32, u32)>
void SvcWrap32(Core::System& system) {
const u32 retval =
func(system, static_cast<u32>(Param(system, 0)), static_cast<u32>(Param(system, 1)),
@@ -621,7 +620,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by WaitForAddress32
-template <ResultCode func(Core::System&, u32, Svc::ArbitrationType, s32, u32, u32)>
+template <Result func(Core::System&, u32, Svc::ArbitrationType, s32, u32, u32)>
void SvcWrap32(Core::System& system) {
const u32 retval = func(system, static_cast<u32>(Param(system, 0)),
static_cast<Svc::ArbitrationType>(Param(system, 1)),
@@ -632,7 +631,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by SignalToAddress32
-template <ResultCode func(Core::System&, u32, Svc::SignalType, s32, s32)>
+template <Result func(Core::System&, u32, Svc::SignalType, s32, s32)>
void SvcWrap32(Core::System& system) {
const u32 retval = func(system, static_cast<u32>(Param(system, 0)),
static_cast<Svc::SignalType>(Param(system, 1)),
@@ -642,13 +641,13 @@ void SvcWrap32(Core::System& system) {
}
// Used by SendSyncRequest32, ArbitrateUnlock32
-template <ResultCode func(Core::System&, u32)>
+template <Result func(Core::System&, u32)>
void SvcWrap32(Core::System& system) {
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw);
}
// Used by CreateTransferMemory32
-template <ResultCode func(Core::System&, Handle*, u32, u32, Svc::MemoryPermission)>
+template <Result func(Core::System&, Handle*, u32, u32, Svc::MemoryPermission)>
void SvcWrap32(Core::System& system) {
Handle handle = 0;
const u32 retval = func(system, &handle, Param32(system, 1), Param32(system, 2),
@@ -659,7 +658,7 @@ void SvcWrap32(Core::System& system) {
}
// Used by WaitSynchronization32
-template <ResultCode func(Core::System&, u32, u32, s32, u32, s32*)>
+template <Result func(Core::System&, u32, u32, s32, u32, s32*)>
void SvcWrap32(Core::System& system) {
s32 param_1 = 0;
const u32 retval = func(system, Param32(system, 0), Param32(system, 1), Param32(system, 2),
@@ -669,4 +668,26 @@ void SvcWrap32(Core::System& system) {
FuncReturn(system, retval);
}
+// Used by CreateCodeMemory32
+template <Result func(Core::System&, Handle*, u32, u32)>
+void SvcWrap32(Core::System& system) {
+ Handle handle = 0;
+
+ const u32 retval = func(system, &handle, Param32(system, 1), Param32(system, 2)).raw;
+
+ system.CurrentArmInterface().SetReg(1, handle);
+ FuncReturn(system, retval);
+}
+
+// Used by ControlCodeMemory32
+template <Result func(Core::System&, Handle, u32, u64, u64, Svc::MemoryPermission)>
+void SvcWrap32(Core::System& system) {
+ const u32 retval =
+ func(system, Param32(system, 0), Param32(system, 1), Param(system, 2), Param(system, 4),
+ static_cast<Svc::MemoryPermission>(Param32(system, 6)))
+ .raw;
+
+ FuncReturn(system, retval);
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/time_manager.cpp b/src/core/hle/kernel/time_manager.cpp
index aa985d820..5ee72c432 100644
--- a/src/core/hle/kernel/time_manager.cpp
+++ b/src/core/hle/kernel/time_manager.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "core/core.h"
@@ -12,19 +11,21 @@
namespace Kernel {
TimeManager::TimeManager(Core::System& system_) : system{system_} {
- time_manager_event_type =
- Core::Timing::CreateEvent("Kernel::TimeManagerCallback",
- [this](std::uintptr_t thread_handle, std::chrono::nanoseconds) {
- KThread* thread = reinterpret_cast<KThread*>(thread_handle);
- {
- KScopedSchedulerLock sl(system.Kernel());
- thread->OnTimer();
- }
- });
+ time_manager_event_type = Core::Timing::CreateEvent(
+ "Kernel::TimeManagerCallback",
+ [this](std::uintptr_t thread_handle, s64 time,
+ std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
+ KThread* thread = reinterpret_cast<KThread*>(thread_handle);
+ {
+ KScopedSchedulerLock sl(system.Kernel());
+ thread->OnTimer();
+ }
+ return std::nullopt;
+ });
}
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 +36,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/kernel/time_manager.h b/src/core/hle/kernel/time_manager.h
index b1fa26e8c..94d16b3b4 100644
--- a/src/core/hle/kernel/time_manager.h
+++ b/src/core/hle/kernel/time_manager.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 3807b9aa8..47a1b829b 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -112,15 +111,16 @@ enum class ErrorModule : u32 {
};
/// Encapsulates a Horizon OS error code, allowing it to be separated into its constituent fields.
-union ResultCode {
+union Result {
u32 raw;
BitField<0, 9, ErrorModule> module;
BitField<9, 13, u32> description;
- constexpr explicit ResultCode(u32 raw_) : raw(raw_) {}
+ Result() = default;
+ constexpr explicit Result(u32 raw_) : raw(raw_) {}
- constexpr ResultCode(ErrorModule module_, u32 description_)
+ constexpr Result(ErrorModule module_, u32 description_)
: raw(module.FormatValue(module_) | description.FormatValue(description_)) {}
[[nodiscard]] constexpr bool IsSuccess() const {
@@ -131,19 +131,20 @@ union ResultCode {
return !IsSuccess();
}
};
+static_assert(std::is_trivial_v<Result>);
-[[nodiscard]] constexpr bool operator==(const ResultCode& a, const ResultCode& b) {
+[[nodiscard]] constexpr bool operator==(const Result& a, const Result& b) {
return a.raw == b.raw;
}
-[[nodiscard]] constexpr bool operator!=(const ResultCode& a, const ResultCode& b) {
+[[nodiscard]] constexpr bool operator!=(const Result& a, const Result& b) {
return !operator==(a, b);
}
// Convenience functions for creating some common kinds of errors:
-/// The default success `ResultCode`.
-constexpr ResultCode ResultSuccess(0);
+/// The default success `Result`.
+constexpr Result ResultSuccess(0);
/**
* Placeholder result code used for unknown error codes.
@@ -151,10 +152,52 @@ constexpr ResultCode ResultSuccess(0);
* @note This should only be used when a particular error code
* is not known yet.
*/
-constexpr ResultCode ResultUnknown(UINT32_MAX);
+constexpr Result ResultUnknown(UINT32_MAX);
/**
- * This is an optional value type. It holds a `ResultCode` and, if that code is ResultSuccess, it
+ * A ResultRange defines an inclusive range of error descriptions within an error module.
+ * This can be used to check whether the description of a given Result falls within the range.
+ * The conversion function returns a Result with its description set to description_start.
+ *
+ * An example of how it could be used:
+ * \code
+ * constexpr ResultRange ResultCommonError{ErrorModule::Common, 0, 9999};
+ *
+ * Result Example(int value) {
+ * const Result result = OtherExample(value);
+ *
+ * // This will only evaluate to true if result.module is ErrorModule::Common and
+ * // result.description is in between 0 and 9999 inclusive.
+ * if (ResultCommonError.Includes(result)) {
+ * // This returns Result{ErrorModule::Common, 0};
+ * return ResultCommonError;
+ * }
+ *
+ * return ResultSuccess;
+ * }
+ * \endcode
+ */
+class ResultRange {
+public:
+ consteval ResultRange(ErrorModule module, u32 description_start, u32 description_end_)
+ : code{module, description_start}, description_end{description_end_} {}
+
+ [[nodiscard]] constexpr operator Result() const {
+ return code;
+ }
+
+ [[nodiscard]] constexpr bool Includes(Result other) const {
+ return code.module == other.module && code.description <= other.description &&
+ other.description <= description_end;
+ }
+
+private:
+ Result code;
+ u32 description_end;
+};
+
+/**
+ * This is an optional value type. It holds a `Result` and, if that code is ResultSuccess, it
* also holds a result of type `T`. If the code is an error code (not ResultSuccess), then trying
* to access the inner value with operator* is undefined behavior and will assert with Unwrap().
* Users of this class must be cognizant to check the status of the ResultVal with operator bool(),
@@ -165,7 +208,7 @@ constexpr ResultCode ResultUnknown(UINT32_MAX);
* ResultVal<int> Frobnicate(float strength) {
* if (strength < 0.f || strength > 1.0f) {
* // Can't frobnicate too weakly or too strongly
- * return ResultCode{ErrorModule::Common, 1};
+ * return Result{ErrorModule::Common, 1};
* } else {
* // Frobnicated! Give caller a cookie
* return 42;
@@ -188,7 +231,9 @@ class ResultVal {
public:
constexpr ResultVal() : expected{} {}
- constexpr ResultVal(ResultCode code) : expected{Common::Unexpected(code)} {}
+ constexpr ResultVal(Result code) : expected{Common::Unexpected(code)} {}
+
+ constexpr ResultVal(ResultRange range) : expected{Common::Unexpected(range)} {}
template <typename U>
constexpr ResultVal(U&& val) : expected{std::forward<U>(val)} {}
@@ -208,7 +253,7 @@ public:
return expected.has_value();
}
- [[nodiscard]] constexpr ResultCode Code() const {
+ [[nodiscard]] constexpr Result Code() const {
return expected.has_value() ? ResultSuccess : expected.error();
}
@@ -275,8 +320,8 @@ public:
}
private:
- // TODO: Replace this with std::expected once it is standardized in the STL.
- Common::Expected<T, ResultCode> expected;
+ // TODO (Morph): Replace this with C++23 std::expected.
+ Common::Expected<T, Result> expected;
};
/**
@@ -293,7 +338,7 @@ private:
target = std::move(*CONCAT2(check_result_L, __LINE__))
/**
- * Analogous to CASCADE_RESULT, but for a bare ResultCode. The code will be propagated if
+ * Analogous to CASCADE_RESULT, but for a bare Result. The code will be propagated if
* non-success, or discarded otherwise.
*/
#define CASCADE_CODE(source) \
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index e34ef5a78..bb838e285 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -16,7 +15,6 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/kernel.h"
#include "core/hle/service/acc/acc.h"
#include "core/hle/service/acc/acc_aa.h"
#include "core/hle/service/acc/acc_su.h"
@@ -30,11 +28,11 @@
namespace Service::Account {
-constexpr ResultCode ERR_INVALID_USER_ID{ErrorModule::Account, 20};
-constexpr ResultCode ERR_INVALID_APPLICATION_ID{ErrorModule::Account, 22};
-constexpr ResultCode ERR_INVALID_BUFFER{ErrorModule::Account, 30};
-constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 31};
-constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100};
+constexpr Result ERR_INVALID_USER_ID{ErrorModule::Account, 20};
+constexpr Result ERR_INVALID_APPLICATION_ID{ErrorModule::Account, 22};
+constexpr Result ERR_INVALID_BUFFER{ErrorModule::Account, 30};
+constexpr Result ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 31};
+constexpr Result ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100};
// Thumbnails are hard coded to be at least this size
constexpr std::size_t THUMBNAIL_SIZE = 0x24000;
@@ -292,7 +290,7 @@ protected:
void Get(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called user_id=0x{}", user_id.RawString());
ProfileBase profile_base{};
- ProfileData data{};
+ UserData data{};
if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) {
ctx.WriteBuffer(data);
IPC::ResponseBuilder rb{ctx, 16};
@@ -375,18 +373,18 @@ protected:
reinterpret_cast<const char*>(base.username.data()), base.username.size()),
base.timestamp, base.user_uuid.RawString());
- if (user_data.size() < sizeof(ProfileData)) {
- LOG_ERROR(Service_ACC, "ProfileData buffer too small!");
+ if (user_data.size() < sizeof(UserData)) {
+ LOG_ERROR(Service_ACC, "UserData buffer too small!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_INVALID_BUFFER);
return;
}
- ProfileData data;
- std::memcpy(&data, user_data.data(), sizeof(ProfileData));
+ UserData data;
+ std::memcpy(&data, user_data.data(), sizeof(UserData));
if (!profile_manager.SetProfileBaseAndData(user_id, base, data)) {
- LOG_ERROR(Service_ACC, "Failed to update profile data and base!");
+ LOG_ERROR(Service_ACC, "Failed to update user data and base!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_FAILED_SAVE_DATA);
return;
@@ -408,15 +406,15 @@ protected:
reinterpret_cast<const char*>(base.username.data()), base.username.size()),
base.timestamp, base.user_uuid.RawString());
- if (user_data.size() < sizeof(ProfileData)) {
- LOG_ERROR(Service_ACC, "ProfileData buffer too small!");
+ if (user_data.size() < sizeof(UserData)) {
+ LOG_ERROR(Service_ACC, "UserData buffer too small!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_INVALID_BUFFER);
return;
}
- ProfileData data;
- std::memcpy(&data, user_data.data(), sizeof(ProfileData));
+ UserData data;
+ std::memcpy(&data, user_data.data(), sizeof(UserData));
Common::FS::IOFile image(GetImagePath(user_id), Common::FS::FileAccessMode::Write,
Common::FS::FileType::BinaryFile);
@@ -507,7 +505,7 @@ protected:
void Cancel() override {}
- ResultCode GetResult() const override {
+ Result GetResult() const override {
return ResultSuccess;
}
};
@@ -536,7 +534,7 @@ public:
private:
void CheckAvailability(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
+ LOG_DEBUG(Service_ACC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(false); // TODO: Check when this is supposed to return true and when not
@@ -749,7 +747,7 @@ void Module::Interface::InitializeApplicationInfoRestricted(Kernel::HLERequestCo
rb.Push(InitializeApplicationInfoBase());
}
-ResultCode Module::Interface::InitializeApplicationInfoBase() {
+Result Module::Interface::InitializeApplicationInfoBase() {
if (application_info) {
LOG_ERROR(Service_ACC, "Application already initialized");
return ERR_ACCOUNTINFO_ALREADY_INITIALIZED;
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index f7e9bc4f8..1621e7c0a 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -42,7 +41,7 @@ public:
void StoreSaveDataThumbnailSystem(Kernel::HLERequestContext& ctx);
private:
- ResultCode InitializeApplicationInfoBase();
+ Result InitializeApplicationInfoBase();
void StoreSaveDataThumbnail(Kernel::HLERequestContext& ctx, const Common::UUID& uuid,
const u64 tid);
diff --git a/src/core/hle/service/acc/acc_aa.cpp b/src/core/hle/service/acc/acc_aa.cpp
index e498fb64d..90ed0f519 100644
--- a/src/core/hle/service/acc/acc_aa.cpp
+++ b/src/core/hle/service/acc/acc_aa.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/acc/acc_aa.h"
diff --git a/src/core/hle/service/acc/acc_aa.h b/src/core/hle/service/acc/acc_aa.h
index d1be20ff3..623daeaef 100644
--- a/src/core/hle/service/acc/acc_aa.h
+++ b/src/core/hle/service/acc/acc_aa.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp
index f4034d591..b6bfd6155 100644
--- a/src/core/hle/service/acc/acc_su.cpp
+++ b/src/core/hle/service/acc/acc_su.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/acc/acc_su.h"
diff --git a/src/core/hle/service/acc/acc_su.h b/src/core/hle/service/acc/acc_su.h
index 132a126b4..8daef38b8 100644
--- a/src/core/hle/service/acc/acc_su.h
+++ b/src/core/hle/service/acc/acc_su.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp
index df77c58f0..65023b8c2 100644
--- a/src/core/hle/service/acc/acc_u0.cpp
+++ b/src/core/hle/service/acc/acc_u0.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/acc/acc_u0.h"
diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h
index 4c2600b67..35cd4b492 100644
--- a/src/core/hle/service/acc/acc_u0.h
+++ b/src/core/hle/service/acc/acc_u0.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp
index 991921984..92f704c2f 100644
--- a/src/core/hle/service/acc/acc_u1.cpp
+++ b/src/core/hle/service/acc/acc_u1.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/acc/acc_u1.h"
diff --git a/src/core/hle/service/acc/acc_u1.h b/src/core/hle/service/acc/acc_u1.h
index 2d478324a..e711d3925 100644
--- a/src/core/hle/service/acc/acc_u1.h
+++ b/src/core/hle/service/acc/acc_u1.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/acc/async_context.cpp b/src/core/hle/service/acc/async_context.cpp
index a49dfdec7..c85b2e43a 100644
--- a/src/core/hle/service/acc/async_context.cpp
+++ b/src/core/hle/service/acc/async_context.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
diff --git a/src/core/hle/service/acc/async_context.h b/src/core/hle/service/acc/async_context.h
index cc3a0a9fe..26332d241 100644
--- a/src/core/hle/service/acc/async_context.h
+++ b/src/core/hle/service/acc/async_context.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -27,7 +26,7 @@ public:
protected:
virtual bool IsComplete() const = 0;
virtual void Cancel() = 0;
- virtual ResultCode GetResult() const = 0;
+ virtual Result GetResult() const = 0;
void MarkComplete();
diff --git a/src/core/hle/service/acc/errors.h b/src/core/hle/service/acc/errors.h
index 1f0577239..e9c16b951 100644
--- a/src/core/hle/service/acc/errors.h
+++ b/src/core/hle/service/acc/errors.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,7 +7,7 @@
namespace Service::Account {
-constexpr ResultCode ERR_ACCOUNTINFO_BAD_APPLICATION{ErrorModule::Account, 22};
-constexpr ResultCode ERR_ACCOUNTINFO_ALREADY_INITIALIZED{ErrorModule::Account, 41};
+constexpr Result ERR_ACCOUNTINFO_BAD_APPLICATION{ErrorModule::Account, 22};
+constexpr Result ERR_ACCOUNTINFO_ALREADY_INITIALIZED{ErrorModule::Account, 41};
} // namespace Service::Account
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index fba847142..a58da4d5f 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <random>
@@ -23,7 +22,7 @@ struct UserRaw {
UUID uuid2{};
u64 timestamp{};
ProfileUsername username{};
- ProfileData extra_data{};
+ UserData extra_data{};
};
static_assert(sizeof(UserRaw) == 0xC8, "UserRaw has incorrect size.");
@@ -34,9 +33,9 @@ struct ProfileDataRaw {
static_assert(sizeof(ProfileDataRaw) == 0x650, "ProfileDataRaw has incorrect size.");
// TODO(ogniK): Get actual error codes
-constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, u32(-1));
-constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, u32(-2));
-constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20);
+constexpr Result ERROR_TOO_MANY_USERS(ErrorModule::Account, u32(-1));
+constexpr Result ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, u32(-2));
+constexpr Result ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20);
constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "system/save/8000000000000010/su/avators";
@@ -88,7 +87,7 @@ bool ProfileManager::RemoveProfileAtIndex(std::size_t index) {
}
/// Helper function to register a user to the system
-ResultCode ProfileManager::AddUser(const ProfileInfo& user) {
+Result ProfileManager::AddUser(const ProfileInfo& user) {
if (!AddToProfiles(user)) {
return ERROR_TOO_MANY_USERS;
}
@@ -97,7 +96,7 @@ ResultCode ProfileManager::AddUser(const ProfileInfo& user) {
/// Create a new user on the system. If the uuid of the user already exists, the user is not
/// created.
-ResultCode ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& username) {
+Result ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& username) {
if (user_count == MAX_USERS) {
return ERROR_TOO_MANY_USERS;
}
@@ -124,7 +123,7 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& usern
/// Creates a new user on the system. This function allows a much simpler method of registration
/// specifically by allowing an std::string for the username. This is required specifically since
/// we're loading a string straight from the config
-ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username) {
+Result ProfileManager::CreateNewUser(UUID uuid, const std::string& username) {
ProfileUsername username_output{};
if (username.size() > username_output.size()) {
@@ -264,7 +263,7 @@ UUID ProfileManager::GetLastOpenedUser() const {
/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
- ProfileData& data) const {
+ UserData& data) const {
if (GetProfileBase(index, profile)) {
data = profiles[*index].data;
return true;
@@ -273,15 +272,14 @@ bool ProfileManager::GetProfileBaseAndData(std::optional<std::size_t> index, Pro
}
/// Return the users profile base and the unknown arbitary data.
-bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile,
- ProfileData& data) const {
+bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile, UserData& data) const {
const auto idx = GetUserIndex(uuid);
return GetProfileBaseAndData(idx, profile, data);
}
/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
- ProfileData& data) const {
+ UserData& data) const {
return GetProfileBaseAndData(user.user_uuid, profile, data);
}
@@ -319,7 +317,7 @@ bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
}
bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new,
- const ProfileData& data_new) {
+ const UserData& data_new) {
const auto index = GetUserIndex(uuid);
if (index.has_value() && SetProfileBase(uuid, profile_new)) {
profiles[*index].data = data_new;
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index 17347f7ef..135f7d0d5 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -1,12 +1,12 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <optional>
+#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "common/uuid.h"
@@ -22,7 +22,7 @@ using UserIDArray = std::array<Common::UUID, MAX_USERS>;
/// Contains extra data related to a user.
/// TODO: RE this structure
-struct ProfileData {
+struct UserData {
INSERT_PADDING_WORDS_NOINIT(1);
u32 icon_id;
u8 bg_color_id;
@@ -30,7 +30,7 @@ struct ProfileData {
INSERT_PADDING_BYTES_NOINIT(0x10);
INSERT_PADDING_BYTES_NOINIT(0x60);
};
-static_assert(sizeof(ProfileData) == 0x80, "ProfileData structure has incorrect size");
+static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size");
/// This holds general information about a users profile. This is where we store all the information
/// based on a specific user
@@ -38,7 +38,7 @@ struct ProfileInfo {
Common::UUID user_uuid{};
ProfileUsername username{};
u64 creation_time{};
- ProfileData data{}; // TODO(ognik): Work out what this is
+ UserData data{}; // TODO(ognik): Work out what this is
bool is_open{};
};
@@ -64,9 +64,9 @@ public:
ProfileManager();
~ProfileManager();
- ResultCode AddUser(const ProfileInfo& user);
- ResultCode CreateNewUser(Common::UUID uuid, const ProfileUsername& username);
- ResultCode CreateNewUser(Common::UUID uuid, const std::string& username);
+ Result AddUser(const ProfileInfo& user);
+ Result CreateNewUser(Common::UUID uuid, const ProfileUsername& username);
+ Result CreateNewUser(Common::UUID uuid, const std::string& username);
std::optional<Common::UUID> GetUser(std::size_t index) const;
std::optional<std::size_t> GetUserIndex(const Common::UUID& uuid) const;
std::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const;
@@ -74,10 +74,9 @@ public:
bool GetProfileBase(Common::UUID uuid, ProfileBase& profile) const;
bool GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const;
bool GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
- ProfileData& data) const;
- bool GetProfileBaseAndData(Common::UUID uuid, ProfileBase& profile, ProfileData& data) const;
- bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
- ProfileData& data) const;
+ UserData& data) const;
+ bool GetProfileBaseAndData(Common::UUID uuid, ProfileBase& profile, UserData& data) const;
+ bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile, UserData& data) const;
std::size_t GetUserCount() const;
std::size_t GetOpenUserCount() const;
bool UserExists(Common::UUID uuid) const;
@@ -93,7 +92,7 @@ public:
bool RemoveUser(Common::UUID uuid);
bool SetProfileBase(Common::UUID uuid, const ProfileBase& profile_new);
bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new,
- const ProfileData& data_new);
+ const UserData& data_new);
private:
void ParseUserSaveFile();
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 773dc9f29..6fb7e198e 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -1,12 +1,10 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstring>
-#include "audio_core/audio_renderer.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
@@ -41,9 +39,9 @@
namespace Service::AM {
-constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 2};
-constexpr ResultCode ERR_NO_MESSAGES{ErrorModule::AM, 3};
-constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 503};
+constexpr Result ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 2};
+constexpr Result ERR_NO_MESSAGES{ErrorModule::AM, 3};
+constexpr Result ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 503};
enum class LaunchParameterKind : u32 {
ApplicationSpecific = 1,
@@ -239,6 +237,7 @@ IDebugFunctions::IDebugFunctions(Core::System& system_)
{130, nullptr, "FriendInvitationSetApplicationParameter"},
{131, nullptr, "FriendInvitationClearApplicationParameter"},
{132, nullptr, "FriendInvitationPushApplicationParameter"},
+ {140, nullptr, "RestrictPowerOperationForSecureLaunchModeForDebug"},
{900, nullptr, "GetGrcProcessLaunchedSystemEvent"},
};
// clang-format on
@@ -286,7 +285,7 @@ ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nv
{62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"},
{63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"},
{64, nullptr, "SetInputDetectionSourceSet"},
- {65, nullptr, "ReportUserIsActive"},
+ {65, &ISelfController::ReportUserIsActive, "ReportUserIsActive"},
{66, nullptr, "GetCurrentIlluminance"},
{67, nullptr, "IsIlluminanceAvailable"},
{68, &ISelfController::SetAutoSleepDisabled, "SetAutoSleepDisabled"},
@@ -366,7 +365,7 @@ void ISelfController::LeaveFatalSection(Kernel::HLERequestContext& ctx) {
// Entry and exit of fatal sections must be balanced.
if (num_fatal_sections_entered == 0) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultCode{ErrorModule::AM, 512});
+ rb.Push(Result{ErrorModule::AM, 512});
return;
}
@@ -518,6 +517,13 @@ void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& c
rb.Push<u32>(idle_time_detection_extension);
}
+void ISelfController::ReportUserIsActive(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void ISelfController::SetAutoSleepDisabled(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
is_auto_sleep_disabled = rp.Pop<bool>();
@@ -618,7 +624,7 @@ void AppletMessageQueue::PushMessage(AppletMessage msg) {
AppletMessageQueue::AppletMessage AppletMessageQueue::PopMessage() {
if (messages.empty()) {
on_new_message->GetWritableEvent().Clear();
- return AppletMessage::NoMessage;
+ return AppletMessage::None;
}
auto msg = messages.front();
messages.pop();
@@ -633,7 +639,11 @@ std::size_t AppletMessageQueue::GetMessageCount() const {
}
void AppletMessageQueue::RequestExit() {
- PushMessage(AppletMessage::ExitRequested);
+ PushMessage(AppletMessage::Exit);
+}
+
+void AppletMessageQueue::RequestResume() {
+ PushMessage(AppletMessage::Resume);
}
void AppletMessageQueue::FocusStateChanged() {
@@ -687,7 +697,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
{66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"},
{67, nullptr, "CancelCpuBoostMode"},
{68, nullptr, "GetBuiltInDisplayType"},
- {80, nullptr, "PerformSystemButtonPressingIfInFocus"},
+ {80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"},
{90, nullptr, "SetPerformanceConfigurationChangedNotification"},
{91, nullptr, "GetCurrentPerformanceConfiguration"},
{100, nullptr, "SetHandlingHomeButtonShortPressedEnabled"},
@@ -732,7 +742,7 @@ void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) {
const auto message = msg_queue->PopMessage();
IPC::ResponseBuilder rb{ctx, 3};
- if (message == AppletMessageQueue::AppletMessage::NoMessage) {
+ if (message == AppletMessageQueue::AppletMessage::None) {
LOG_ERROR(Service_AM, "Message queue is empty");
rb.Push(ERR_NO_MESSAGES);
rb.PushEnum<AppletMessageQueue::AppletMessage>(message);
@@ -744,7 +754,7 @@ void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) {
}
void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ LOG_DEBUG(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@@ -827,6 +837,16 @@ void ICommonStateGetter::SetCpuBoostMode(Kernel::HLERequestContext& ctx) {
apm_sys->SetCpuBoostMode(ctx);
}
+void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto system_button{rp.PopEnum<SystemButtonType>()};
+
+ LOG_WARNING(Service_AM, "(STUBBED) called, system_button={}", system_button);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(
Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -980,7 +1000,7 @@ private:
LOG_DEBUG(Service_AM, "called");
IPC::RequestParser rp{ctx};
- applet->GetBroker().PushNormalDataFromGame(rp.PopIpcInterface<IStorage>());
+ applet->GetBroker().PushNormalDataFromGame(rp.PopIpcInterface<IStorage>().lock());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -1007,7 +1027,7 @@ private:
LOG_DEBUG(Service_AM, "called");
IPC::RequestParser rp{ctx};
- applet->GetBroker().PushInteractiveDataFromGame(rp.PopIpcInterface<IStorage>());
+ applet->GetBroker().PushInteractiveDataFromGame(rp.PopIpcInterface<IStorage>().lock());
ASSERT(applet->IsInitialized());
applet->ExecuteInteractive();
@@ -1301,6 +1321,8 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
{33, &IApplicationFunctions::EndBlockingHomeButton, "EndBlockingHomeButton"},
{34, nullptr, "SelectApplicationLicense"},
{35, nullptr, "GetDeviceSaveDataSizeMax"},
+ {36, nullptr, "GetLimitedApplicationLicense"},
+ {37, nullptr, "GetLimitedApplicationLicenseUpgradableEvent"},
{40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"},
{50, &IApplicationFunctions::GetPseudoDeviceId, "GetPseudoDeviceId"},
{60, nullptr, "SetMediaPlaybackStateForApplication"},
@@ -1337,7 +1359,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 +1809,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 2a578aea5..bb75c6281 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -22,6 +21,7 @@ class NVFlinger;
namespace Service::AM {
+// This is nn::settings::Language
enum SystemLanguage {
Japanese = 0,
English = 1, // en-US
@@ -41,16 +41,44 @@ enum SystemLanguage {
// 4.0.0+
SimplifiedChinese = 15,
TraditionalChinese = 16,
+ // 10.1.0+
+ BrazilianPortuguese = 17,
};
class AppletMessageQueue {
public:
+ // This is nn::am::AppletMessage
enum class AppletMessage : u32 {
- NoMessage = 0,
- ExitRequested = 4,
+ None = 0,
+ ChangeIntoForeground = 1,
+ ChangeIntoBackground = 2,
+ Exit = 4,
+ ApplicationExited = 6,
FocusStateChanged = 15,
+ Resume = 16,
+ DetectShortPressingHomeButton = 20,
+ DetectLongPressingHomeButton = 21,
+ DetectShortPressingPowerButton = 22,
+ DetectMiddlePressingPowerButton = 23,
+ DetectLongPressingPowerButton = 24,
+ RequestToPrepareSleep = 25,
+ FinishedSleepSequence = 26,
+ SleepRequiredByHighTemperature = 27,
+ SleepRequiredByLowBattery = 28,
+ AutoPowerDown = 29,
OperationModeChanged = 30,
PerformanceModeChanged = 31,
+ DetectReceivingCecSystemStandby = 32,
+ SdCardRemoved = 33,
+ LaunchApplicationRequested = 50,
+ RequestToDisplay = 51,
+ ShowApplicationLogo = 55,
+ HideApplicationLogo = 56,
+ ForceHideApplicationLogo = 57,
+ FloatingApplicationDetected = 60,
+ DetectShortPressingCaptureButton = 90,
+ AlbumScreenShotTaken = 92,
+ AlbumRecordingSaved = 93,
};
explicit AppletMessageQueue(Core::System& system);
@@ -62,6 +90,7 @@ public:
AppletMessage PopMessage();
std::size_t GetMessageCount() const;
void RequestExit();
+ void RequestResume();
void FocusStateChanged();
void OperationModeChanged();
@@ -146,6 +175,7 @@ private:
void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx);
void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
+ void ReportUserIsActive(Kernel::HLERequestContext& ctx);
void SetAutoSleepDisabled(Kernel::HLERequestContext& ctx);
void IsAutoSleepDisabled(Kernel::HLERequestContext& ctx);
void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx);
@@ -179,16 +209,31 @@ public:
~ICommonStateGetter() override;
private:
+ // This is nn::oe::FocusState
enum class FocusState : u8 {
InFocus = 1,
NotInFocus = 2,
+ Background = 3,
};
+ // This is nn::oe::OperationMode
enum class OperationMode : u8 {
Handheld = 0,
Docked = 1,
};
+ // This is nn::am::service::SystemButtonType
+ enum class SystemButtonType {
+ None,
+ HomeButtonShortPressing,
+ HomeButtonLongPressing,
+ PowerButtonShortPressing,
+ PowerButtonLongPressing,
+ ShutdownSystem,
+ CaptureButtonShortPressing,
+ CaptureButtonLongPressing,
+ };
+
void GetEventHandle(Kernel::HLERequestContext& ctx);
void ReceiveMessage(Kernel::HLERequestContext& ctx);
void GetCurrentFocusState(Kernel::HLERequestContext& ctx);
@@ -203,6 +248,7 @@ private:
void EndVrModeEx(Kernel::HLERequestContext& ctx);
void GetDefaultDisplayResolution(Kernel::HLERequestContext& ctx);
void SetCpuBoostMode(Kernel::HLERequestContext& ctx);
+ void PerformSystemButtonPressingIfInFocus(Kernel::HLERequestContext& ctx);
void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(Kernel::HLERequestContext& ctx);
std::shared_ptr<AppletMessageQueue> msg_queue;
@@ -304,6 +350,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/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index 0ec4fd4ca..d7719da35 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/core.h"
diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h
index f89f65649..2147976a6 100644
--- a/src/core/hle/service/am/applet_ae.h
+++ b/src/core/hle/service/am/applet_ae.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp
index b8859f4e6..00fc4202c 100644
--- a/src/core/hle/service/am/applet_oe.cpp
+++ b/src/core/hle/service/am/applet_oe.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h
index 64b874ead..8fea249f1 100644
--- a/src/core/hle/service/am/applet_oe.h
+++ b/src/core/hle/service/am/applet_oe.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/am/applets/applet_controller.cpp b/src/core/hle/service/am/applets/applet_controller.cpp
index d073f2210..b418031de 100644
--- a/src/core/hle/service/am/applets/applet_controller.cpp
+++ b/src/core/hle/service/am/applets/applet_controller.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
@@ -21,9 +20,9 @@
namespace Service::AM::Applets {
// This error code (0x183ACA) is thrown when the applet fails to initialize.
-[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3101{ErrorModule::HID, 3101};
+[[maybe_unused]] constexpr Result ERR_CONTROLLER_APPLET_3101{ErrorModule::HID, 3101};
// This error code (0x183CCA) is thrown when the u32 result in ControllerSupportResultInfo is 2.
-[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102};
+[[maybe_unused]] constexpr Result ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102};
static Core::Frontend::ControllerParameters ConvertToFrontendParameters(
ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text,
@@ -174,12 +173,12 @@ bool Controller::TransactionComplete() const {
return complete;
}
-ResultCode Controller::GetStatus() const {
+Result Controller::GetStatus() const {
return status;
}
void Controller::ExecuteInteractive() {
- UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet.");
+ ASSERT_MSG(false, "Attempted to call interactive execution on non-interactive applet.");
}
void Controller::Execute() {
diff --git a/src/core/hle/service/am/applets/applet_controller.h b/src/core/hle/service/am/applets/applet_controller.h
index 1a832505e..1f9adec65 100644
--- a/src/core/hle/service/am/applets/applet_controller.h
+++ b/src/core/hle/service/am/applets/applet_controller.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -127,7 +126,7 @@ public:
void Initialize() override;
bool TransactionComplete() const override;
- ResultCode GetStatus() const override;
+ Result GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
@@ -144,7 +143,7 @@ private:
ControllerUpdateFirmwareArg controller_update_arg;
ControllerKeyRemappingArg controller_key_remapping_arg;
bool complete{false};
- ResultCode status{ResultSuccess};
+ Result status{ResultSuccess};
bool is_single_mode{false};
std::vector<u8> out_data;
};
diff --git a/src/core/hle/service/am/applets/applet_error.cpp b/src/core/hle/service/am/applets/applet_error.cpp
index a06c2b872..fcf34bf7e 100644
--- a/src/core/hle/service/am/applets/applet_error.cpp
+++ b/src/core/hle/service/am/applets/applet_error.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <cstring>
@@ -26,15 +25,15 @@ struct ErrorCode {
};
}
- static constexpr ErrorCode FromResultCode(ResultCode result) {
+ static constexpr ErrorCode FromResult(Result result) {
return {
.error_category{2000 + static_cast<u32>(result.module.Value())},
.error_number{result.description.Value()},
};
}
- constexpr ResultCode ToResultCode() const {
- return ResultCode{static_cast<ErrorModule>(error_category - 2000), error_number};
+ constexpr Result ToResult() const {
+ return Result{static_cast<ErrorModule>(error_category - 2000), error_number};
}
};
static_assert(sizeof(ErrorCode) == 0x8, "ErrorCode has incorrect size.");
@@ -98,8 +97,8 @@ void CopyArgumentData(const std::vector<u8>& data, T& variable) {
std::memcpy(&variable, data.data(), sizeof(T));
}
-ResultCode Decode64BitError(u64 error) {
- return ErrorCode::FromU64(error).ToResultCode();
+Result Decode64BitError(u64 error) {
+ return ErrorCode::FromU64(error).ToResult();
}
} // Anonymous namespace
@@ -128,16 +127,16 @@ void Error::Initialize() {
if (args->error.use_64bit_error_code) {
error_code = Decode64BitError(args->error.error_code_64);
} else {
- error_code = ResultCode(args->error.error_code_32);
+ error_code = Result(args->error.error_code_32);
}
break;
case ErrorAppletMode::ShowSystemError:
CopyArgumentData(data, args->system_error);
- error_code = ResultCode(Decode64BitError(args->system_error.error_code_64));
+ error_code = Result(Decode64BitError(args->system_error.error_code_64));
break;
case ErrorAppletMode::ShowApplicationError:
CopyArgumentData(data, args->application_error);
- error_code = ResultCode(args->application_error.error_code);
+ error_code = Result(args->application_error.error_code);
break;
case ErrorAppletMode::ShowErrorRecord:
CopyArgumentData(data, args->error_record);
@@ -152,12 +151,12 @@ bool Error::TransactionComplete() const {
return complete;
}
-ResultCode Error::GetStatus() const {
+Result Error::GetStatus() const {
return ResultSuccess;
}
void Error::ExecuteInteractive() {
- UNREACHABLE_MSG("Unexpected interactive applet data!");
+ ASSERT_MSG(false, "Unexpected interactive applet data!");
}
void Error::Execute() {
diff --git a/src/core/hle/service/am/applets/applet_error.h b/src/core/hle/service/am/applets/applet_error.h
index 8aa9046a5..d78d6f1d1 100644
--- a/src/core/hle/service/am/applets/applet_error.h
+++ b/src/core/hle/service/am/applets/applet_error.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -32,7 +31,7 @@ public:
void Initialize() override;
bool TransactionComplete() const override;
- ResultCode GetStatus() const override;
+ Result GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
@@ -42,7 +41,7 @@ private:
union ErrorArguments;
const Core::Frontend::ErrorApplet& frontend;
- ResultCode error_code = ResultSuccess;
+ Result error_code = ResultSuccess;
ErrorAppletMode mode = ErrorAppletMode::ShowError;
std::unique_ptr<ErrorArguments> args;
diff --git a/src/core/hle/service/am/applets/applet_general_backend.cpp b/src/core/hle/service/am/applets/applet_general_backend.cpp
index 2c6e9d83c..c34ef08b3 100644
--- a/src/core/hle/service/am/applets/applet_general_backend.cpp
+++ b/src/core/hle/service/am/applets/applet_general_backend.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/hex_util.h"
@@ -14,7 +13,7 @@
namespace Service::AM::Applets {
-constexpr ResultCode ERROR_INVALID_PIN{ErrorModule::PCTL, 221};
+constexpr Result ERROR_INVALID_PIN{ErrorModule::PCTL, 221};
static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) {
std::shared_ptr<IStorage> storage = broker.PopNormalDataToApplet();
@@ -72,12 +71,12 @@ bool Auth::TransactionComplete() const {
return complete;
}
-ResultCode Auth::GetStatus() const {
+Result Auth::GetStatus() const {
return successful ? ResultSuccess : ERROR_INVALID_PIN;
}
void Auth::ExecuteInteractive() {
- UNREACHABLE_MSG("Unexpected interactive applet data.");
+ ASSERT_MSG(false, "Unexpected interactive applet data.");
}
void Auth::Execute() {
@@ -137,7 +136,7 @@ void Auth::AuthFinished(bool is_successful) {
successful = is_successful;
struct Return {
- ResultCode result_code;
+ Result result_code;
};
static_assert(sizeof(Return) == 0x4, "Return (AuthApplet) has incorrect size.");
@@ -171,12 +170,12 @@ bool PhotoViewer::TransactionComplete() const {
return complete;
}
-ResultCode PhotoViewer::GetStatus() const {
+Result PhotoViewer::GetStatus() const {
return ResultSuccess;
}
void PhotoViewer::ExecuteInteractive() {
- UNREACHABLE_MSG("Unexpected interactive applet data.");
+ ASSERT_MSG(false, "Unexpected interactive applet data.");
}
void PhotoViewer::Execute() {
@@ -224,7 +223,7 @@ bool StubApplet::TransactionComplete() const {
return true;
}
-ResultCode StubApplet::GetStatus() const {
+Result StubApplet::GetStatus() const {
LOG_WARNING(Service_AM, "called (STUBBED)");
return ResultSuccess;
}
diff --git a/src/core/hle/service/am/applets/applet_general_backend.h b/src/core/hle/service/am/applets/applet_general_backend.h
index 7496ded88..a9f2535a2 100644
--- a/src/core/hle/service/am/applets/applet_general_backend.h
+++ b/src/core/hle/service/am/applets/applet_general_backend.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -26,7 +25,7 @@ public:
void Initialize() override;
bool TransactionComplete() const override;
- ResultCode GetStatus() const override;
+ Result GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
@@ -57,7 +56,7 @@ public:
void Initialize() override;
bool TransactionComplete() const override;
- ResultCode GetStatus() const override;
+ Result GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
@@ -78,7 +77,7 @@ public:
void Initialize() override;
bool TransactionComplete() const override;
- ResultCode GetStatus() const override;
+ Result GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
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..ae80ef506
--- /dev/null
+++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp
@@ -0,0 +1,138 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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;
+}
+
+Result MiiEdit::GetStatus() const {
+ return ResultSuccess;
+}
+
+void MiiEdit::ExecuteInteractive() {
+ ASSERT_MSG(false, "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..d18dd3cf5
--- /dev/null
+++ b/src/core/hle/service/am/applets/applet_mii_edit.h
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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;
+ Result 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..4705d019f
--- /dev/null
+++ b/src/core/hle/service/am/applets/applet_mii_edit_types.h
@@ -0,0 +1,82 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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::CharInfo 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_profile_select.cpp b/src/core/hle/service/am/applets/applet_profile_select.cpp
index 82500e121..c738db028 100644
--- a/src/core/hle/service/am/applets/applet_profile_select.cpp
+++ b/src/core/hle/service/am/applets/applet_profile_select.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
@@ -13,7 +12,7 @@
namespace Service::AM::Applets {
-constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1};
+constexpr Result ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1};
ProfileSelect::ProfileSelect(Core::System& system_, LibraryAppletMode applet_mode_,
const Core::Frontend::ProfileSelectApplet& frontend_)
@@ -40,12 +39,12 @@ bool ProfileSelect::TransactionComplete() const {
return complete;
}
-ResultCode ProfileSelect::GetStatus() const {
+Result ProfileSelect::GetStatus() const {
return status;
}
void ProfileSelect::ExecuteInteractive() {
- UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet.");
+ ASSERT_MSG(false, "Attempted to call interactive execution on non-interactive applet.");
}
void ProfileSelect::Execute() {
diff --git a/src/core/hle/service/am/applets/applet_profile_select.h b/src/core/hle/service/am/applets/applet_profile_select.h
index 852e1e0c0..b77f1d205 100644
--- a/src/core/hle/service/am/applets/applet_profile_select.h
+++ b/src/core/hle/service/am/applets/applet_profile_select.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -40,7 +39,7 @@ public:
void Initialize() override;
bool TransactionComplete() const override;
- ResultCode GetStatus() const override;
+ Result GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
@@ -51,7 +50,7 @@ private:
UserSelectionConfig config;
bool complete = false;
- ResultCode status = ResultSuccess;
+ Result status = ResultSuccess;
std::vector<u8> final_data;
Core::System& system;
};
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..c18236045 100644
--- a/src/core/hle/service/am/applets/applet_software_keyboard.cpp
+++ b/src/core/hle/service/am/applets/applet_software_keyboard.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/string_util.h"
#include "core/core.h"
@@ -72,7 +71,7 @@ void SoftwareKeyboard::Initialize() {
InitializeBackground(applet_mode);
break;
default:
- UNREACHABLE_MSG("Invalid LibraryAppletMode={}", applet_mode);
+ ASSERT_MSG(false, "Invalid LibraryAppletMode={}", applet_mode);
break;
}
}
@@ -81,7 +80,7 @@ bool SoftwareKeyboard::TransactionComplete() const {
return complete;
}
-ResultCode SoftwareKeyboard::GetStatus() const {
+Result SoftwareKeyboard::GetStatus() const {
return status;
}
@@ -226,7 +225,7 @@ void SoftwareKeyboard::InitializeForeground() {
ASSERT(work_buffer_storage != nullptr);
if (swkbd_config_common.initial_string_length == 0) {
- InitializeFrontendKeyboard();
+ InitializeFrontendNormalKeyboard();
return;
}
@@ -243,7 +242,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 +479,179 @@ 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},
+ .left_optional_symbol_key{swkbd_config_common.left_optional_symbol_key},
+ .right_optional_symbol_key{swkbd_config_common.right_optional_symbol_key},
+ .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},
+ .left_optional_symbol_key{appear_arg.left_optional_symbol_key},
+ .right_optional_symbol_key{appear_arg.right_optional_symbol_key},
+ .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},
+ .left_optional_symbol_key{appear_arg.left_optional_symbol_key},
+ .right_optional_symbol_key{appear_arg.right_optional_symbol_key},
+ .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 +663,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 +690,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 +782,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 +793,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_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;
+ }
- std::memcpy(&swkbd_calc_arg, request_data.data() + sizeof(SwkbdRequestCommand),
- sizeof(SwkbdCalcArg));
+ 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..b01b31c98 100644
--- a/src/core/hle/service/am/applets/applet_software_keyboard.h
+++ b/src/core/hle/service/am/applets/applet_software_keyboard.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -13,6 +12,11 @@ namespace Core {
class System;
}
+namespace Core::Frontend {
+struct KeyboardInitializeParameters;
+struct InlineAppearParameters;
+} // namespace Core::Frontend
+
namespace Service::AM::Applets {
class SoftwareKeyboard final : public Applet {
@@ -24,7 +28,7 @@ public:
void Initialize() override;
bool TransactionComplete() const override;
- ResultCode GetStatus() const override;
+ Result GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
@@ -78,13 +82,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 +107,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 +127,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 +167,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};
@@ -160,7 +180,7 @@ private:
bool is_background{false};
bool complete{false};
- ResultCode status{ResultSuccess};
+ Result status{ResultSuccess};
};
} // namespace Service::AM::Applets
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..1f696900e 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
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,6 +9,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 +216,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 +229,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 +321,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..14aa6f69e 100644
--- a/src/core/hle/service/am/applets/applet_web_browser.cpp
+++ b/src/core/hle/service/am/applets/applet_web_browser.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/fs/file.h"
@@ -22,7 +21,7 @@
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applet_web_browser.h"
#include "core/hle/service/filesystem/filesystem.h"
-#include "core/hle/service/ns/pl_u.h"
+#include "core/hle/service/ns/iplatform_service_manager.h"
#include "core/loader/loader.h"
namespace Service::AM::Applets {
@@ -280,7 +279,7 @@ void WebBrowser::Initialize() {
InitializeLobby();
break;
default:
- UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind);
+ ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind);
break;
}
}
@@ -289,7 +288,7 @@ bool WebBrowser::TransactionComplete() const {
return complete;
}
-ResultCode WebBrowser::GetStatus() const {
+Result WebBrowser::GetStatus() const {
return status;
}
@@ -321,7 +320,7 @@ void WebBrowser::Execute() {
ExecuteLobby();
break;
default:
- UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind);
+ ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind);
WebBrowserExit(WebExitReason::EndButtonPressed);
break;
}
@@ -446,6 +445,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/applet_web_browser.h b/src/core/hle/service/am/applets/applet_web_browser.h
index b3364ee06..fd727fac8 100644
--- a/src/core/hle/service/am/applets/applet_web_browser.h
+++ b/src/core/hle/service/am/applets/applet_web_browser.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -33,7 +32,7 @@ public:
void Initialize() override;
bool TransactionComplete() const override;
- ResultCode GetStatus() const override;
+ Result GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
@@ -67,7 +66,7 @@ private:
const Core::Frontend::WebBrowserApplet& frontend;
bool complete{false};
- ResultCode status{ResultSuccess};
+ Result status{ResultSuccess};
WebAppletVersion web_applet_version{};
WebArgHeader web_arg_header{};
diff --git a/src/core/hle/service/am/applets/applet_web_browser_types.h b/src/core/hle/service/am/applets/applet_web_browser_types.h
index 419c2bf79..c522c5c1a 100644
--- a/src/core/hle/service/am/applets/applet_web_browser_types.h
+++ b/src/core/hle/service/am/applets/applet_web_browser_types.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index 134ac1ee2..b5b8e4cad 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
@@ -9,6 +8,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_edit.h"
#include "core/frontend/applets/profile_select.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/frontend/applets/web_browser.h"
@@ -19,6 +19,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_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"
@@ -171,11 +172,12 @@ void Applet::Initialize() {
AppletFrontendSet::AppletFrontendSet() = default;
AppletFrontendSet::AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
+ MiiEdit mii_edit_,
ParentalControlsApplet parental_controls_applet,
PhotoViewer photo_viewer_, ProfileSelect profile_select_,
SoftwareKeyboard software_keyboard_, WebBrowser web_browser_)
: controller{std::move(controller_applet)}, error{std::move(error_applet)},
- parental_controls{std::move(parental_controls_applet)},
+ 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_)} {}
@@ -202,6 +204,10 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
frontend.error = std::move(set.error);
}
+ if (set.mii_edit != nullptr) {
+ frontend.mii_edit = std::move(set.mii_edit);
+ }
+
if (set.parental_controls != nullptr) {
frontend.parental_controls = std::move(set.parental_controls);
}
@@ -238,6 +244,10 @@ 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>();
@@ -277,6 +287,8 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, LibraryAppletMode
return std::make_shared<ProfileSelect>(system, mode, *frontend.profile_select);
case AppletId::SoftwareKeyboard:
return std::make_shared<SoftwareKeyboard>(system, mode, *frontend.software_keyboard);
+ case AppletId::MiiEdit:
+ 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 15eeb4ee1..e78a57657 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,7 +9,7 @@
#include "common/swap.h"
#include "core/hle/service/kernel_helpers.h"
-union ResultCode;
+union Result;
namespace Core {
class System;
@@ -20,6 +19,7 @@ namespace Core::Frontend {
class ControllerApplet;
class ECommerceApplet;
class ErrorApplet;
+class MiiEditApplet;
class ParentalControlsApplet;
class PhotoViewerApplet;
class ProfileSelectApplet;
@@ -138,7 +138,7 @@ public:
virtual void Initialize();
virtual bool TransactionComplete() const = 0;
- virtual ResultCode GetStatus() const = 0;
+ virtual Result GetStatus() const = 0;
virtual void ExecuteInteractive() = 0;
virtual void Execute() = 0;
@@ -178,6 +178,7 @@ 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 PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>;
@@ -186,9 +187,9 @@ struct AppletFrontendSet {
AppletFrontendSet();
AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
- ParentalControlsApplet parental_controls_applet, PhotoViewer photo_viewer_,
- ProfileSelect profile_select_, SoftwareKeyboard software_keyboard_,
- WebBrowser web_browser_);
+ MiiEdit mii_edit_, ParentalControlsApplet parental_controls_applet,
+ PhotoViewer photo_viewer_, ProfileSelect profile_select_,
+ SoftwareKeyboard software_keyboard_, WebBrowser web_browser_);
~AppletFrontendSet();
AppletFrontendSet(const AppletFrontendSet&) = delete;
@@ -199,6 +200,7 @@ struct AppletFrontendSet {
ControllerApplet controller;
ErrorApplet error;
+ MiiEdit mii_edit;
ParentalControlsApplet parental_controls;
PhotoViewer photo_viewer;
ProfileSelect profile_select;
diff --git a/src/core/hle/service/am/idle.cpp b/src/core/hle/service/am/idle.cpp
index 6196773d5..603515284 100644
--- a/src/core/hle/service/am/idle.cpp
+++ b/src/core/hle/service/am/idle.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/am/idle.h"
diff --git a/src/core/hle/service/am/idle.h b/src/core/hle/service/am/idle.h
index e290c30b1..15b31f67e 100644
--- a/src/core/hle/service/am/idle.h
+++ b/src/core/hle/service/am/idle.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/am/omm.cpp b/src/core/hle/service/am/omm.cpp
index 6da9b9f58..66824e495 100644
--- a/src/core/hle/service/am/omm.cpp
+++ b/src/core/hle/service/am/omm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/am/omm.h"
diff --git a/src/core/hle/service/am/omm.h b/src/core/hle/service/am/omm.h
index 3766150fe..73d0c82d5 100644
--- a/src/core/hle/service/am/omm.h
+++ b/src/core/hle/service/am/omm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/am/spsm.cpp b/src/core/hle/service/am/spsm.cpp
index 95218d9ee..ec581e32b 100644
--- a/src/core/hle/service/am/spsm.cpp
+++ b/src/core/hle/service/am/spsm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/am/spsm.h"
diff --git a/src/core/hle/service/am/spsm.h b/src/core/hle/service/am/spsm.h
index 04bbf9e68..922f8863e 100644
--- a/src/core/hle/service/am/spsm.h
+++ b/src/core/hle/service/am/spsm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/am/tcap.cpp b/src/core/hle/service/am/tcap.cpp
index 4d0971c03..818420e22 100644
--- a/src/core/hle/service/am/tcap.cpp
+++ b/src/core/hle/service/am/tcap.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/am/tcap.h"
diff --git a/src/core/hle/service/am/tcap.h b/src/core/hle/service/am/tcap.h
index e9578f16e..6b2148c29 100644
--- a/src/core/hle/service/am/tcap.h
+++ b/src/core/hle/service/am/tcap.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index 3c83717b5..368ccd52f 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <numeric>
diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h
index 4b5f7c5f2..6c1ce601a 100644
--- a/src/core/hle/service/aoc/aoc_u.h
+++ b/src/core/hle/service/aoc/aoc_u.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp
index 243ea15b8..8a338d9b1 100644
--- a/src/core/hle/service/apm/apm.cpp
+++ b/src/core/hle/service/apm/apm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/hle/service/apm/apm.h"
diff --git a/src/core/hle/service/apm/apm.h b/src/core/hle/service/apm/apm.h
index 691fe6c16..0fecc766a 100644
--- a/src/core/hle/service/apm/apm.h
+++ b/src/core/hle/service/apm/apm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/apm/apm_controller.cpp b/src/core/hle/service/apm/apm_controller.cpp
index 98839fe97..d6de84066 100644
--- a/src/core/hle/service/apm/apm_controller.cpp
+++ b/src/core/hle/service/apm/apm_controller.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -17,8 +16,8 @@ constexpr auto DEFAULT_PERFORMANCE_CONFIGURATION = PerformanceConfiguration::Con
Controller::Controller(Core::Timing::CoreTiming& core_timing_)
: core_timing{core_timing_}, configs{
- {PerformanceMode::Handheld, DEFAULT_PERFORMANCE_CONFIGURATION},
- {PerformanceMode::Docked, DEFAULT_PERFORMANCE_CONFIGURATION},
+ {PerformanceMode::Normal, DEFAULT_PERFORMANCE_CONFIGURATION},
+ {PerformanceMode::Boost, DEFAULT_PERFORMANCE_CONFIGURATION},
} {}
Controller::~Controller() = default;
@@ -63,13 +62,13 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) {
PerformanceConfiguration::Config15,
}};
- SetPerformanceConfiguration(PerformanceMode::Docked,
+ SetPerformanceConfiguration(PerformanceMode::Boost,
BOOST_MODE_TO_CONFIG_MAP.at(static_cast<u32>(mode)));
}
PerformanceMode Controller::GetCurrentPerformanceMode() const {
- return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Docked
- : PerformanceMode::Handheld;
+ return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Boost
+ : PerformanceMode::Normal;
}
PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) {
@@ -81,7 +80,7 @@ PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(Performa
}
void Controller::SetClockSpeed(u32 mhz) {
- LOG_INFO(Service_APM, "called, mhz={:08X}", mhz);
+ LOG_DEBUG(Service_APM, "called, mhz={:08X}", mhz);
// TODO(DarkLordZach): Actually signal core_timing to change clock speed.
// TODO(Rodrigo): Remove [[maybe_unused]] when core_timing is used.
}
diff --git a/src/core/hle/service/apm/apm_controller.h b/src/core/hle/service/apm/apm_controller.h
index 8d48e0104..3357b7762 100644
--- a/src/core/hle/service/apm/apm_controller.h
+++ b/src/core/hle/service/apm/apm_controller.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -32,15 +31,18 @@ enum class PerformanceConfiguration : u32 {
Config16 = 0x9222000C,
};
+// This is nn::oe::CpuBoostMode
enum class CpuBoostMode : u32 {
- Disabled = 0,
- Full = 1, // CPU + GPU -> Config 13, 14, 15, or 16
- Partial = 2, // GPU Only -> Config 15 or 16
+ Normal = 0, // Boost mode disabled
+ FastLoad = 1, // CPU + GPU -> Config 13, 14, 15, or 16
+ Partial = 2, // GPU Only -> Config 15 or 16
};
-enum class PerformanceMode : u8 {
- Handheld = 0,
- Docked = 1,
+// This is nn::oe::PerformanceMode
+enum class PerformanceMode : s32 {
+ Invalid = -1,
+ Normal = 0,
+ Boost = 1,
};
// Class to manage the state and change of the emulated system performance.
diff --git a/src/core/hle/service/apm/apm_interface.cpp b/src/core/hle/service/apm/apm_interface.cpp
index 6163e3294..041fc16bd 100644
--- a/src/core/hle/service/apm/apm_interface.cpp
+++ b/src/core/hle/service/apm/apm_interface.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
diff --git a/src/core/hle/service/apm/apm_interface.h b/src/core/hle/service/apm/apm_interface.h
index 063ad5308..0740fd4ba 100644
--- a/src/core/hle/service/apm/apm_interface.h
+++ b/src/core/hle/service/apm/apm_interface.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp
index 260fd0e0e..4a2ae5f88 100644
--- a/src/core/hle/service/audio/audctl.cpp
+++ b/src/core/hle/service/audio/audctl.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
diff --git a/src/core/hle/service/audio/audctl.h b/src/core/hle/service/audio/audctl.h
index 15f6c77a0..a27ff6cfe 100644
--- a/src/core/hle/service/audio/audctl.h
+++ b/src/core/hle/service/audio/audctl.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/audio/auddbg.cpp b/src/core/hle/service/audio/auddbg.cpp
index 6264e4bda..5541af300 100644
--- a/src/core/hle/service/audio/auddbg.cpp
+++ b/src/core/hle/service/audio/auddbg.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/audio/auddbg.h"
diff --git a/src/core/hle/service/audio/auddbg.h b/src/core/hle/service/audio/auddbg.h
index d1653eedd..8f26be5dc 100644
--- a/src/core/hle/service/audio/auddbg.h
+++ b/src/core/hle/service/audio/auddbg.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/audio/audin_a.cpp b/src/core/hle/service/audio/audin_a.cpp
index 10acaad19..98f4a6048 100644
--- a/src/core/hle/service/audio/audin_a.cpp
+++ b/src/core/hle/service/audio/audin_a.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/audio/audin_a.h"
diff --git a/src/core/hle/service/audio/audin_a.h b/src/core/hle/service/audio/audin_a.h
index 15120a4b6..19a927de5 100644
--- a/src/core/hle/service/audio/audin_a.h
+++ b/src/core/hle/service/audio/audin_a.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp
index 34cc659ed..48a9a73a0 100644
--- a/src/core/hle/service/audio/audin_u.cpp
+++ b/src/core/hle/service/audio/audin_u.cpp
@@ -1,69 +1,211 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include "audio_core/in/audio_in_system.h"
+#include "audio_core/renderer/audio_device.h"
+#include "common/common_funcs.h"
#include "common/logging/log.h"
+#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/audio/audin_u.h"
namespace Service::Audio {
+using namespace AudioCore::AudioIn;
-IAudioIn::IAudioIn(Core::System& system_)
- : ServiceFramework{system_, "IAudioIn"}, service_context{system_, "IAudioIn"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "GetAudioInState"},
- {1, &IAudioIn::Start, "Start"},
- {2, nullptr, "Stop"},
- {3, nullptr, "AppendAudioInBuffer"},
- {4, &IAudioIn::RegisterBufferEvent, "RegisterBufferEvent"},
- {5, nullptr, "GetReleasedAudioInBuffer"},
- {6, nullptr, "ContainsAudioInBuffer"},
- {7, nullptr, "AppendUacInBuffer"},
- {8, &IAudioIn::AppendAudioInBufferAuto, "AppendAudioInBufferAuto"},
- {9, nullptr, "GetReleasedAudioInBuffersAuto"},
- {10, nullptr, "AppendUacInBufferAuto"},
- {11, nullptr, "GetAudioInBufferCount"},
- {12, nullptr, "SetDeviceGain"},
- {13, nullptr, "GetDeviceGain"},
- {14, nullptr, "FlushAudioInBuffers"},
- };
- // clang-format on
+class IAudioIn final : public ServiceFramework<IAudioIn> {
+public:
+ explicit IAudioIn(Core::System& system_, Manager& manager, size_t session_id,
+ std::string& device_name, const AudioInParameter& in_params, u32 handle,
+ u64 applet_resource_user_id)
+ : ServiceFramework{system_, "IAudioIn"},
+ service_context{system_, "IAudioIn"}, event{service_context.CreateEvent("AudioInEvent")},
+ impl{std::make_shared<In>(system_, manager, event, session_id)} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IAudioIn::GetAudioInState, "GetAudioInState"},
+ {1, &IAudioIn::Start, "Start"},
+ {2, &IAudioIn::Stop, "Stop"},
+ {3, &IAudioIn::AppendAudioInBuffer, "AppendAudioInBuffer"},
+ {4, &IAudioIn::RegisterBufferEvent, "RegisterBufferEvent"},
+ {5, &IAudioIn::GetReleasedAudioInBuffer, "GetReleasedAudioInBuffer"},
+ {6, &IAudioIn::ContainsAudioInBuffer, "ContainsAudioInBuffer"},
+ {7, &IAudioIn::AppendAudioInBuffer, "AppendUacInBuffer"},
+ {8, &IAudioIn::AppendAudioInBuffer, "AppendAudioInBufferAuto"},
+ {9, &IAudioIn::GetReleasedAudioInBuffer, "GetReleasedAudioInBuffersAuto"},
+ {10, &IAudioIn::AppendAudioInBuffer, "AppendUacInBufferAuto"},
+ {11, &IAudioIn::GetAudioInBufferCount, "GetAudioInBufferCount"},
+ {12, &IAudioIn::SetDeviceGain, "SetDeviceGain"},
+ {13, &IAudioIn::GetDeviceGain, "GetDeviceGain"},
+ {14, &IAudioIn::FlushAudioInBuffers, "FlushAudioInBuffers"},
+ };
+ // clang-format on
- RegisterHandlers(functions);
+ RegisterHandlers(functions);
- buffer_event = service_context.CreateEvent("IAudioIn:BufferEvent");
-}
+ if (impl->GetSystem()
+ .Initialize(device_name, in_params, handle, applet_resource_user_id)
+ .IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to initialize the AudioIn System!");
+ }
+ }
-IAudioIn::~IAudioIn() {
- service_context.CloseEvent(buffer_event);
-}
+ ~IAudioIn() override {
+ impl->Free();
+ service_context.CloseEvent(event);
+ }
-void IAudioIn::Start(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ [[nodiscard]] std::shared_ptr<In> GetImpl() {
+ return impl;
+ }
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
+private:
+ void GetAudioInState(Kernel::HLERequestContext& ctx) {
+ const auto state = static_cast<u32>(impl->GetState());
-void IAudioIn::RegisterBufferEvent(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ LOG_DEBUG(Service_Audio, "called. State={}", state);
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(buffer_event->GetReadableEvent());
-}
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(state);
+ }
-void IAudioIn::AppendAudioInBufferAuto(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ void Start(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "called");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
+ auto result = impl->StartSystem();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void Stop(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "called");
+
+ auto result = impl->StopSystem();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void AppendAudioInBuffer(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ u64 tag = rp.PopRaw<u64>();
-AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} {
+ const auto in_buffer_size{ctx.GetReadBufferSize()};
+ if (in_buffer_size < sizeof(AudioInBuffer)) {
+ LOG_ERROR(Service_Audio, "Input buffer is too small for an AudioInBuffer!");
+ }
+
+ const auto& in_buffer = ctx.ReadBuffer();
+ AudioInBuffer buffer{};
+ std::memcpy(&buffer, in_buffer.data(), sizeof(AudioInBuffer));
+
+ [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()};
+ LOG_TRACE(Service_Audio, "called. Session {} Appending buffer {:08X}", sessionid, tag);
+
+ auto result = impl->AppendBuffer(buffer, tag);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void RegisterBufferEvent(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "called");
+
+ auto& buffer_event = impl->GetBufferEvent();
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(buffer_event);
+ }
+
+ void GetReleasedAudioInBuffer(Kernel::HLERequestContext& ctx) {
+ auto write_buffer_size = ctx.GetWriteBufferSize() / sizeof(u64);
+ std::vector<u64> released_buffers(write_buffer_size, 0);
+
+ auto count = impl->GetReleasedBuffers(released_buffers);
+
+ [[maybe_unused]] std::string tags{};
+ for (u32 i = 0; i < count; i++) {
+ tags += fmt::format("{:08X}, ", released_buffers[i]);
+ }
+ [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()};
+ LOG_TRACE(Service_Audio, "called. Session {} released {} buffers: {}", sessionid, count,
+ tags);
+
+ ctx.WriteBuffer(released_buffers);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(count);
+ }
+
+ void ContainsAudioInBuffer(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const u64 tag{rp.Pop<u64>()};
+ const auto buffer_queued{impl->ContainsAudioBuffer(tag)};
+
+ LOG_DEBUG(Service_Audio, "called. Is buffer {:08X} registered? {}", tag, buffer_queued);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(buffer_queued);
+ }
+
+ void GetAudioInBufferCount(Kernel::HLERequestContext& ctx) {
+ const auto buffer_count = impl->GetBufferCount();
+
+ LOG_DEBUG(Service_Audio, "called. Buffer count={}", buffer_count);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+
+ rb.Push(ResultSuccess);
+ rb.Push(buffer_count);
+ }
+
+ void SetDeviceGain(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const auto volume{rp.Pop<f32>()};
+ LOG_DEBUG(Service_Audio, "called. Gain {}", volume);
+
+ impl->SetVolume(volume);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void GetDeviceGain(Kernel::HLERequestContext& ctx) {
+ auto volume{impl->GetVolume()};
+
+ LOG_DEBUG(Service_Audio, "called. Gain {}", volume);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(volume);
+ }
+
+ void FlushAudioInBuffers(Kernel::HLERequestContext& ctx) {
+ bool flushed{impl->FlushAudioInBuffers()};
+
+ LOG_DEBUG(Service_Audio, "called. Were any buffers flushed? {}", flushed);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(flushed);
+ }
+
+ KernelHelpers::ServiceContext service_context;
+ Kernel::KEvent* event;
+ std::shared_ptr<AudioCore::AudioIn::In> impl;
+};
+
+AudInU::AudInU(Core::System& system_)
+ : ServiceFramework{system_, "audin:u", ServiceThreadType::CreateNew},
+ service_context{system_, "AudInU"}, impl{std::make_unique<AudioCore::AudioIn::Manager>(
+ system_)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &AudInU::ListAudioIns, "ListAudioIns"},
@@ -81,59 +223,152 @@ AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} {
AudInU::~AudInU() = default;
void AudInU::ListAudioIns(Kernel::HLERequestContext& ctx) {
+ using namespace AudioCore::AudioRenderer;
+
LOG_DEBUG(Service_Audio, "called");
- const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioInDeviceName);
- const std::size_t device_count = std::min(count, audio_device_names.size());
- std::vector<AudioInDeviceName> device_names;
- device_names.reserve(device_count);
+ const auto write_count =
+ static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName));
+ std::vector<AudioDevice::AudioDeviceName> device_names{};
- for (std::size_t i = 0; i < device_count; i++) {
- const auto& device_name = audio_device_names[i];
- auto& entry = device_names.emplace_back();
- device_name.copy(entry.data(), device_name.size());
+ u32 out_count{0};
+ if (write_count > 0) {
+ out_count = impl->GetDeviceNames(device_names, write_count, false);
+ ctx.WriteBuffer(device_names);
}
- ctx.WriteBuffer(device_names);
-
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(static_cast<u32>(device_names.size()));
+ rb.Push(out_count);
}
void AudInU::ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx) {
+ using namespace AudioCore::AudioRenderer;
+
LOG_DEBUG(Service_Audio, "called");
- constexpr u32 device_count = 0;
- // Since we don't actually use any other audio input devices, we return 0 devices. Filtered
- // device listing just omits the default input device
+ const auto write_count =
+ static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName));
+ std::vector<AudioDevice::AudioDeviceName> device_names{};
+
+ u32 out_count{0};
+ if (write_count > 0) {
+ out_count = impl->GetDeviceNames(device_names, write_count, true);
+ ctx.WriteBuffer(device_names);
+ }
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(static_cast<u32>(device_count));
+ rb.Push(out_count);
}
-void AudInU::OpenInOutImpl(Kernel::HLERequestContext& ctx) {
- AudInOutParams params{};
- params.channel_count = 2;
- params.sample_format = SampleFormat::PCM16;
- params.sample_rate = 48000;
- params.state = State::Started;
+void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto in_params{rp.PopRaw<AudioInParameter>()};
+ auto applet_resource_user_id{rp.PopRaw<u64>()};
+ const auto device_name_data{ctx.ReadBuffer()};
+ auto device_name = Common::StringFromBuffer(device_name_data);
+ auto handle{ctx.GetCopyHandle(0)};
+
+ std::scoped_lock l{impl->mutex};
+ auto link{impl->LinkToManager()};
+ if (link.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to link Audio In to Audio Manager");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(link);
+ return;
+ }
+
+ size_t new_session_id{};
+ auto result{impl->AcquireSessionId(new_session_id)};
+ if (result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id,
+ impl->num_free_sessions);
+
+ auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name,
+ in_params, handle, applet_resource_user_id);
+ impl->sessions[new_session_id] = audio_in->GetImpl();
+ impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id;
+
+ auto& out_system = impl->sessions[new_session_id]->GetSystem();
+ AudioInParameterInternal out_params{.sample_rate = out_system.GetSampleRate(),
+ .channel_count = out_system.GetChannelCount(),
+ .sample_format =
+ static_cast<u32>(out_system.GetSampleFormat()),
+ .state = static_cast<u32>(out_system.GetState())};
IPC::ResponseBuilder rb{ctx, 6, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushRaw<AudInOutParams>(params);
- rb.PushIpcInterface<IAudioIn>(system);
-}
-void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
- OpenInOutImpl(ctx);
+ std::string out_name{out_system.GetName()};
+ ctx.WriteBuffer(out_name);
+
+ rb.Push(ResultSuccess);
+ rb.PushRaw<AudioInParameterInternal>(out_params);
+ rb.PushIpcInterface<IAudioIn>(audio_in);
}
void AudInU::OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
- OpenInOutImpl(ctx);
+ IPC::RequestParser rp{ctx};
+ auto protocol_specified{rp.PopRaw<u64>()};
+ auto in_params{rp.PopRaw<AudioInParameter>()};
+ auto applet_resource_user_id{rp.PopRaw<u64>()};
+ const auto device_name_data{ctx.ReadBuffer()};
+ auto device_name = Common::StringFromBuffer(device_name_data);
+ auto handle{ctx.GetCopyHandle(0)};
+
+ std::scoped_lock l{impl->mutex};
+ auto link{impl->LinkToManager()};
+ if (link.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to link Audio In to Audio Manager");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(link);
+ return;
+ }
+
+ size_t new_session_id{};
+ auto result{impl->AcquireSessionId(new_session_id)};
+ if (result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id,
+ impl->num_free_sessions);
+
+ auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name,
+ in_params, handle, applet_resource_user_id);
+ impl->sessions[new_session_id] = audio_in->GetImpl();
+ impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id;
+
+ auto& out_system = impl->sessions[new_session_id]->GetSystem();
+ AudioInParameterInternal out_params{.sample_rate = out_system.GetSampleRate(),
+ .channel_count = out_system.GetChannelCount(),
+ .sample_format =
+ static_cast<u32>(out_system.GetSampleFormat()),
+ .state = static_cast<u32>(out_system.GetState())};
+
+ IPC::ResponseBuilder rb{ctx, 6, 0, 1};
+
+ std::string out_name{out_system.GetName()};
+ if (protocol_specified == 0) {
+ if (out_system.IsUac()) {
+ out_name = "UacIn";
+ } else {
+ out_name = "DeviceIn";
+ }
+ }
+
+ ctx.WriteBuffer(out_name);
+
+ rb.Push(ResultSuccess);
+ rb.PushRaw<AudioInParameterInternal>(out_params);
+ rb.PushIpcInterface<IAudioIn>(audio_in);
}
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h
index bf3418613..b45fda78a 100644
--- a/src/core/hle/service/audio/audin_u.h
+++ b/src/core/hle/service/audio/audin_u.h
@@ -1,9 +1,10 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include "audio_core/audio_in_manager.h"
+#include "audio_core/in/audio_in.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/service.h"
@@ -15,22 +16,12 @@ namespace Kernel {
class HLERequestContext;
}
-namespace Service::Audio {
-
-class IAudioIn final : public ServiceFramework<IAudioIn> {
-public:
- explicit IAudioIn(Core::System& system_);
- ~IAudioIn() override;
-
-private:
- void Start(Kernel::HLERequestContext& ctx);
- void RegisterBufferEvent(Kernel::HLERequestContext& ctx);
- void AppendAudioInBufferAuto(Kernel::HLERequestContext& ctx);
-
- KernelHelpers::ServiceContext service_context;
+namespace AudioCore::AudioOut {
+class Manager;
+class In;
+} // namespace AudioCore::AudioOut
- Kernel::KEvent* buffer_event;
-};
+namespace Service::Audio {
class AudInU final : public ServiceFramework<AudInU> {
public:
@@ -38,33 +29,14 @@ public:
~AudInU() override;
private:
- enum class SampleFormat : u32_le {
- PCM16 = 2,
- };
-
- enum class State : u32_le {
- Started = 0,
- Stopped = 1,
- };
-
- struct AudInOutParams {
- u32_le sample_rate{};
- u32_le channel_count{};
- SampleFormat sample_format{};
- State state{};
- };
- static_assert(sizeof(AudInOutParams) == 0x10, "AudInOutParams is an invalid size");
-
- using AudioInDeviceName = std::array<char, 256>;
- static constexpr std::array<std::string_view, 1> audio_device_names{{
- "BuiltInHeadset",
- }};
-
void ListAudioIns(Kernel::HLERequestContext& ctx);
void ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx);
void OpenInOutImpl(Kernel::HLERequestContext& ctx);
void OpenAudioIn(Kernel::HLERequestContext& ctx);
void OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx);
+
+ KernelHelpers::ServiceContext service_context;
+ std::unique_ptr<AudioCore::AudioIn::Manager> impl;
};
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp
index b3f24f9bb..97da71dfa 100644
--- a/src/core/hle/service/audio/audio.cpp
+++ b/src/core/hle/service/audio/audio.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/audio/audctl.h"
#include "core/hle/service/audio/auddbg.h"
diff --git a/src/core/hle/service/audio/audio.h b/src/core/hle/service/audio/audio.h
index b6d13912e..bbb2214e4 100644
--- a/src/core/hle/service/audio/audio.h
+++ b/src/core/hle/service/audio/audio.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/audio/audout_a.cpp b/src/core/hle/service/audio/audout_a.cpp
index 3ee522b50..5ecb99236 100644
--- a/src/core/hle/service/audio/audout_a.cpp
+++ b/src/core/hle/service/audio/audout_a.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/audio/audout_a.h"
diff --git a/src/core/hle/service/audio/audout_a.h b/src/core/hle/service/audio/audout_a.h
index 2043dfb77..f641cffeb 100644
--- a/src/core/hle/service/audio/audout_a.h
+++ b/src/core/hle/service/audio/audout_a.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index affa7971c..49c092301 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -1,60 +1,47 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <cstring>
#include <vector>
-#include "audio_core/audio_out.h"
-#include "audio_core/codec.h"
+#include "audio_core/out/audio_out_system.h"
+#include "audio_core/renderer/audio_device.h"
#include "common/common_funcs.h"
#include "common/logging/log.h"
+#include "common/string_util.h"
#include "common/swap.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/audio/audout_u.h"
#include "core/hle/service/audio/errors.h"
-#include "core/hle/service/kernel_helpers.h"
#include "core/memory.h"
namespace Service::Audio {
-
-constexpr std::array<char, 10> DefaultDevice{{"DeviceOut"}};
-constexpr int DefaultSampleRate{48000};
-
-struct AudoutParams {
- s32_le sample_rate;
- u16_le channel_count;
- INSERT_PADDING_BYTES_NOINIT(2);
-};
-static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size");
-
-enum class AudioState : u32 {
- Started,
- Stopped,
-};
+using namespace AudioCore::AudioOut;
class IAudioOut final : public ServiceFramework<IAudioOut> {
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"} {
+ explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager,
+ size_t session_id, std::string& device_name,
+ const AudioOutParameter& in_params, u32 handle, u64 applet_resource_user_id)
+ : ServiceFramework{system_, "IAudioOut", ServiceThreadType::CreateNew},
+ service_context{system_, "IAudioOut"}, event{service_context.CreateEvent(
+ "AudioOutEvent")},
+ impl{std::make_shared<AudioCore::AudioOut::Out>(system_, manager, event, session_id)} {
+
// clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
- {1, &IAudioOut::StartAudioOut, "Start"},
- {2, &IAudioOut::StopAudioOut, "Stop"},
- {3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"},
+ {1, &IAudioOut::Start, "Start"},
+ {2, &IAudioOut::Stop, "Stop"},
+ {3, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBuffer"},
{4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"},
- {5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffers"},
+ {5, &IAudioOut::GetReleasedAudioOutBuffers, "GetReleasedAudioOutBuffers"},
{6, &IAudioOut::ContainsAudioOutBuffer, "ContainsAudioOutBuffer"},
- {7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"},
- {8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"},
+ {7, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBufferAuto"},
+ {8, &IAudioOut::GetReleasedAudioOutBuffers, "GetReleasedAudioOutBuffersAuto"},
{9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"},
{10, &IAudioOut::GetAudioOutPlayedSampleCount, "GetAudioOutPlayedSampleCount"},
{11, &IAudioOut::FlushAudioOutBuffers, "FlushAudioOutBuffers"},
@@ -64,241 +51,262 @@ public:
// clang-format on
RegisterHandlers(functions);
- // This is the event handle used to check if the audio buffer was released
- buffer_event = service_context.CreateEvent("IAudioOutBufferReleased");
-
- stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate,
- audio_params.channel_count, std::move(unique_name), [this] {
- const auto guard = LockService();
- buffer_event->GetWritableEvent().Signal();
- });
+ if (impl->GetSystem()
+ .Initialize(device_name, in_params, handle, applet_resource_user_id)
+ .IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to initialize the AudioOut System!");
+ }
}
~IAudioOut() override {
- service_context.CloseEvent(buffer_event);
+ impl->Free();
+ service_context.CloseEvent(event);
}
-private:
- struct AudioBuffer {
- u64_le next;
- u64_le buffer;
- u64_le buffer_capacity;
- u64_le buffer_size;
- u64_le offset;
- };
- static_assert(sizeof(AudioBuffer) == 0x28, "AudioBuffer is an invalid size");
+ [[nodiscard]] std::shared_ptr<AudioCore::AudioOut::Out> GetImpl() {
+ return impl;
+ }
+private:
void GetAudioOutState(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
+ const auto state = static_cast<u32>(impl->GetState());
+
+ LOG_DEBUG(Service_Audio, "called. State={}", state);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(static_cast<u32>(stream->IsPlaying() ? AudioState::Started : AudioState::Stopped));
+ rb.Push(state);
}
- void StartAudioOut(Kernel::HLERequestContext& ctx) {
+ void Start(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
- if (stream->IsPlaying()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_OPERATION_FAILED);
- return;
- }
-
- audio_core.StartStream(stream);
+ auto result = impl->StartSystem();
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
- void StopAudioOut(Kernel::HLERequestContext& ctx) {
+ void Stop(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
- if (stream->IsPlaying()) {
- audio_core.StopStream(stream);
- }
+ auto result = impl->StopSystem();
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
- void RegisterBufferEvent(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(buffer_event->GetReadableEvent());
- }
-
- void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "(STUBBED) called {}", ctx.Description());
+ void AppendAudioOutBuffer(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
+ u64 tag = rp.PopRaw<u64>();
- const auto& input_buffer{ctx.ReadBuffer()};
- ASSERT_MSG(input_buffer.size() == sizeof(AudioBuffer),
- "AudioBuffer input is an invalid size!");
- AudioBuffer audio_buffer{};
- std::memcpy(&audio_buffer, input_buffer.data(), sizeof(AudioBuffer));
- const u64 tag{rp.Pop<u64>()};
+ const auto in_buffer_size{ctx.GetReadBufferSize()};
+ if (in_buffer_size < sizeof(AudioOutBuffer)) {
+ LOG_ERROR(Service_Audio, "Input buffer is too small for an AudioOutBuffer!");
+ }
- std::vector<s16> samples(audio_buffer.buffer_size / sizeof(s16));
- main_memory.ReadBlock(audio_buffer.buffer, samples.data(), audio_buffer.buffer_size);
+ const auto& in_buffer = ctx.ReadBuffer();
+ AudioOutBuffer buffer{};
+ std::memcpy(&buffer, in_buffer.data(), sizeof(AudioOutBuffer));
- if (!audio_core.QueueBuffer(stream, tag, std::move(samples))) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_BUFFER_COUNT_EXCEEDED);
- return;
- }
+ [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()};
+ LOG_TRACE(Service_Audio, "called. Session {} Appending buffer {:08X}", sessionid, tag);
+
+ auto result = impl->AppendBuffer(buffer, tag);
IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void RegisterBufferEvent(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "called");
+
+ auto& buffer_event = impl->GetBufferEvent();
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
+ rb.PushCopyObjects(buffer_event);
}
- void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called {}", ctx.Description());
+ void GetReleasedAudioOutBuffers(Kernel::HLERequestContext& ctx) {
+ auto write_buffer_size = ctx.GetWriteBufferSize() / sizeof(u64);
+ std::vector<u64> released_buffers(write_buffer_size, 0);
- const u64 max_count{ctx.GetWriteBufferSize() / sizeof(u64)};
- const auto released_buffers{audio_core.GetTagsAndReleaseBuffers(stream, max_count)};
+ auto count = impl->GetReleasedBuffers(released_buffers);
- std::vector<u64> tags{released_buffers};
- tags.resize(max_count);
- ctx.WriteBuffer(tags);
+ [[maybe_unused]] std::string tags{};
+ for (u32 i = 0; i < count; i++) {
+ tags += fmt::format("{:08X}, ", released_buffers[i]);
+ }
+ [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()};
+ LOG_TRACE(Service_Audio, "called. Session {} released {} buffers: {}", sessionid, count,
+ tags);
+ ctx.WriteBuffer(released_buffers);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(released_buffers.size()));
+ rb.Push(count);
}
void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
-
IPC::RequestParser rp{ctx};
+
const u64 tag{rp.Pop<u64>()};
+ const auto buffer_queued{impl->ContainsAudioBuffer(tag)};
+
+ LOG_DEBUG(Service_Audio, "called. Is buffer {:08X} registered? {}", tag, buffer_queued);
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(stream->ContainsBuffer(tag));
+ rb.Push(buffer_queued);
}
void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
+ const auto buffer_count = impl->GetBufferCount();
+
+ LOG_DEBUG(Service_Audio, "called. Buffer count={}", buffer_count);
IPC::ResponseBuilder rb{ctx, 3};
+
rb.Push(ResultSuccess);
- rb.Push(static_cast<u32>(stream->GetQueueSize()));
+ rb.Push(buffer_count);
}
void GetAudioOutPlayedSampleCount(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
+ const auto samples_played = impl->GetPlayedSampleCount();
+
+ LOG_DEBUG(Service_Audio, "called. Played samples={}", samples_played);
IPC::ResponseBuilder rb{ctx, 4};
+
rb.Push(ResultSuccess);
- rb.Push(stream->GetPlayedSampleCount());
+ rb.Push(samples_played);
}
void FlushAudioOutBuffers(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
+ bool flushed{impl->FlushAudioOutBuffers()};
+
+ LOG_DEBUG(Service_Audio, "called. Were any buffers flushed? {}", flushed);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(stream->Flush());
+ rb.Push(flushed);
}
void SetAudioOutVolume(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const float volume = rp.Pop<float>();
- LOG_DEBUG(Service_Audio, "called, volume={}", volume);
+ const auto volume = rp.Pop<f32>();
+
+ LOG_DEBUG(Service_Audio, "called. Volume={}", volume);
- stream->SetVolume(volume);
+ impl->SetVolume(volume);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void GetAudioOutVolume(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
+ const auto volume = impl->GetVolume();
+
+ LOG_DEBUG(Service_Audio, "called. Volume={}", volume);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(stream->GetVolume());
+ rb.Push(volume);
}
- AudioCore::AudioOut& audio_core;
- AudioCore::StreamPtr stream;
- std::string device_name;
-
- [[maybe_unused]] AudoutParams audio_params{};
-
- Core::Memory::Memory& main_memory;
-
KernelHelpers::ServiceContext service_context;
-
- /// This is the event handle used to check if the audio buffer was released
- Kernel::KEvent* buffer_event;
+ Kernel::KEvent* event;
+ std::shared_ptr<AudioCore::AudioOut::Out> impl;
};
-AudOutU::AudOutU(Core::System& system_) : ServiceFramework{system_, "audout:u"} {
+AudOutU::AudOutU(Core::System& system_)
+ : ServiceFramework{system_, "audout:u", ServiceThreadType::CreateNew},
+ service_context{system_, "AudOutU"}, impl{std::make_unique<AudioCore::AudioOut::Manager>(
+ system_)} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"},
- {1, &AudOutU::OpenAudioOutImpl, "OpenAudioOut"},
- {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"},
- {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"},
+ {0, &AudOutU::ListAudioOuts, "ListAudioOuts"},
+ {1, &AudOutU::OpenAudioOut, "OpenAudioOut"},
+ {2, &AudOutU::ListAudioOuts, "ListAudioOutsAuto"},
+ {3, &AudOutU::OpenAudioOut, "OpenAudioOutAuto"},
};
// clang-format on
RegisterHandlers(functions);
- audio_core = std::make_unique<AudioCore::AudioOut>();
}
AudOutU::~AudOutU() = default;
-void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
+void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
+ using namespace AudioCore::AudioRenderer;
- ctx.WriteBuffer(DefaultDevice);
+ std::scoped_lock l{impl->mutex};
+
+ const auto write_count =
+ static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName));
+ std::vector<AudioDevice::AudioDeviceName> device_names{};
+ if (write_count > 0) {
+ device_names.emplace_back("DeviceOut");
+ LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut");
+ } else {
+ LOG_DEBUG(Service_Audio, "called. Empty buffer passed in.");
+ }
+
+ ctx.WriteBuffer(device_names);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push<u32>(1); // Amount of audio devices
+ rb.Push<u32>(static_cast<u32>(device_names.size()));
}
-void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
-
+void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto in_params{rp.PopRaw<AudioOutParameter>()};
+ auto applet_resource_user_id{rp.PopRaw<u64>()};
const auto device_name_data{ctx.ReadBuffer()};
- std::string device_name;
- if (device_name_data[0] != '\0') {
- device_name.assign(device_name_data.begin(), device_name_data.end());
- } else {
- device_name.assign(DefaultDevice.begin(), DefaultDevice.end());
- }
- ctx.WriteBuffer(device_name);
+ auto device_name = Common::StringFromBuffer(device_name_data);
+ auto handle{ctx.GetCopyHandle(0)};
- IPC::RequestParser rp{ctx};
- auto params{rp.PopRaw<AudoutParams>()};
- if (params.channel_count <= 2) {
- // Mono does not exist for audout
- params.channel_count = 2;
- } else {
- params.channel_count = 6;
+ auto link{impl->LinkToManager()};
+ if (link.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to link Audio Out to Audio Manager");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(link);
+ return;
}
- if (!params.sample_rate) {
- params.sample_rate = DefaultSampleRate;
+
+ size_t new_session_id{};
+ auto result{impl->AcquireSessionId(new_session_id)};
+ if (result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
}
- std::string unique_name{fmt::format("{}-{}", device_name, audio_out_interfaces.size())};
- auto audio_out_interface = std::make_shared<IAudioOut>(
- system, params, *audio_core, std::move(device_name), std::move(unique_name));
+ LOG_DEBUG(Service_Audio, "Opening new AudioOut, sessionid={}, free sessions={}", new_session_id,
+ impl->num_free_sessions);
+
+ auto audio_out = std::make_shared<IAudioOut>(system, *impl, new_session_id, device_name,
+ in_params, handle, applet_resource_user_id);
+
+ impl->sessions[new_session_id] = audio_out->GetImpl();
+ impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id;
+
+ auto& out_system = impl->sessions[new_session_id]->GetSystem();
+ AudioOutParameterInternal out_params{.sample_rate = out_system.GetSampleRate(),
+ .channel_count = out_system.GetChannelCount(),
+ .sample_format =
+ static_cast<u32>(out_system.GetSampleFormat()),
+ .state = static_cast<u32>(out_system.GetState())};
IPC::ResponseBuilder rb{ctx, 6, 0, 1};
- rb.Push(ResultSuccess);
- rb.Push<u32>(DefaultSampleRate);
- rb.Push<u32>(params.channel_count);
- rb.Push<u32>(static_cast<u32>(AudioCore::Codec::PcmFormat::Int16));
- rb.Push<u32>(static_cast<u32>(AudioState::Stopped));
- rb.PushIpcInterface<IAudioOut>(audio_out_interface);
- audio_out_interfaces.push_back(std::move(audio_out_interface));
+ ctx.WriteBuffer(out_system.GetName());
+
+ rb.Push(ResultSuccess);
+ rb.PushRaw<AudioOutParameterInternal>(out_params);
+ rb.PushIpcInterface<IAudioOut>(audio_out);
}
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h
index f7ae2f2bf..fdc0ee754 100644
--- a/src/core/hle/service/audio/audout_u.h
+++ b/src/core/hle/service/audio/audout_u.h
@@ -1,16 +1,13 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <vector>
+#include "audio_core/audio_out_manager.h"
+#include "audio_core/out/audio_out.h"
+#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/service.h"
-namespace AudioCore {
-class AudioOut;
-}
-
namespace Core {
class System;
}
@@ -19,6 +16,11 @@ namespace Kernel {
class HLERequestContext;
}
+namespace AudioCore::AudioOut {
+class Manager;
+class Out;
+} // namespace AudioCore::AudioOut
+
namespace Service::Audio {
class IAudioOut;
@@ -29,11 +31,11 @@ public:
~AudOutU() override;
private:
- void ListAudioOutsImpl(Kernel::HLERequestContext& ctx);
- void OpenAudioOutImpl(Kernel::HLERequestContext& ctx);
+ void ListAudioOuts(Kernel::HLERequestContext& ctx);
+ void OpenAudioOut(Kernel::HLERequestContext& ctx);
- std::vector<std::shared_ptr<IAudioOut>> audio_out_interfaces;
- std::unique_ptr<AudioCore::AudioOut> audio_core;
+ KernelHelpers::ServiceContext service_context;
+ std::unique_ptr<AudioCore::AudioOut::Manager> impl;
};
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audrec_a.cpp b/src/core/hle/service/audio/audrec_a.cpp
index 70fc17ae2..fa82e9ac7 100644
--- a/src/core/hle/service/audio/audrec_a.cpp
+++ b/src/core/hle/service/audio/audrec_a.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/audio/audrec_a.h"
diff --git a/src/core/hle/service/audio/audrec_a.h b/src/core/hle/service/audio/audrec_a.h
index 2cce90b1d..9edf89f6c 100644
--- a/src/core/hle/service/audio/audrec_a.h
+++ b/src/core/hle/service/audio/audrec_a.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/audio/audrec_u.cpp b/src/core/hle/service/audio/audrec_u.cpp
index 74a65ccff..bc55cec17 100644
--- a/src/core/hle/service/audio/audrec_u.cpp
+++ b/src/core/hle/service/audio/audrec_u.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/audio/audrec_u.h"
diff --git a/src/core/hle/service/audio/audrec_u.h b/src/core/hle/service/audio/audrec_u.h
index f79d49e5c..8b4817884 100644
--- a/src/core/hle/service/audio/audrec_u.h
+++ b/src/core/hle/service/audio/audrec_u.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/audio/audren_a.cpp b/src/core/hle/service/audio/audren_a.cpp
index cf8c34a15..e775ac3bf 100644
--- a/src/core/hle/service/audio/audren_a.cpp
+++ b/src/core/hle/service/audio/audren_a.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/audio/audren_a.h"
diff --git a/src/core/hle/service/audio/audren_a.h b/src/core/hle/service/audio/audren_a.h
index 5d0a626ad..9e08b4245 100644
--- a/src/core/hle/service/audio/audren_a.h
+++ b/src/core/hle/service/audio/audren_a.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index f45e5cecc..6fb07c37d 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -1,11 +1,15 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <memory>
-#include "audio_core/audio_renderer.h"
+#include "audio_core/audio_core.h"
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/common/feature_support.h"
+#include "audio_core/renderer/audio_device.h"
+#include "audio_core/renderer/audio_renderer.h"
+#include "audio_core/renderer/voice/voice_info.h"
#include "common/alignment.h"
#include "common/bit_util.h"
#include "common/common_funcs.h"
@@ -14,90 +18,127 @@
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/service/audio/audren_u.h"
#include "core/hle/service/audio/errors.h"
+#include "core/memory.h"
+
+using namespace AudioCore::AudioRenderer;
namespace Service::Audio {
class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
public:
- explicit IAudioRenderer(Core::System& system_,
- const AudioCommon::AudioRendererParameter& audren_params,
- const std::size_t instance_number)
- : ServiceFramework{system_, "IAudioRenderer"}, service_context{system_, "IAudioRenderer"} {
+ explicit IAudioRenderer(Core::System& system_, Manager& manager_,
+ AudioCore::AudioRendererParameterInternal& params,
+ Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
+ u32 process_handle, u64 applet_resource_user_id, s32 session_id)
+ : ServiceFramework{system_, "IAudioRenderer", ServiceThreadType::CreateNew},
+ service_context{system_, "IAudioRenderer"}, rendered_event{service_context.CreateEvent(
+ "IAudioRendererEvent")},
+ manager{manager_}, impl{std::make_unique<Renderer>(system_, manager, rendered_event)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioRenderer::GetSampleRate, "GetSampleRate"},
{1, &IAudioRenderer::GetSampleCount, "GetSampleCount"},
{2, &IAudioRenderer::GetMixBufferCount, "GetMixBufferCount"},
{3, &IAudioRenderer::GetState, "GetState"},
- {4, &IAudioRenderer::RequestUpdateImpl, "RequestUpdate"},
+ {4, &IAudioRenderer::RequestUpdate, "RequestUpdate"},
{5, &IAudioRenderer::Start, "Start"},
{6, &IAudioRenderer::Stop, "Stop"},
{7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"},
{8, &IAudioRenderer::SetRenderingTimeLimit, "SetRenderingTimeLimit"},
{9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"},
- {10, &IAudioRenderer::RequestUpdateImpl, "RequestUpdateAuto"},
- {11, &IAudioRenderer::ExecuteAudioRendererRendering, "ExecuteAudioRendererRendering"},
+ {10, &IAudioRenderer::RequestUpdate, "RequestUpdateAuto"},
+ {11, nullptr, "ExecuteAudioRendererRendering"},
};
// clang-format on
RegisterHandlers(functions);
- system_event = service_context.CreateEvent("IAudioRenderer:SystemEvent");
- renderer = std::make_unique<AudioCore::AudioRenderer>(
- system.CoreTiming(), system.Memory(), audren_params,
- [this]() {
- const auto guard = LockService();
- system_event->GetWritableEvent().Signal();
- },
- instance_number);
+ impl->Initialize(params, transfer_memory, transfer_memory_size, process_handle,
+ applet_resource_user_id, session_id);
}
~IAudioRenderer() override {
- service_context.CloseEvent(system_event);
+ impl->Finalize();
+ service_context.CloseEvent(rendered_event);
}
private:
void GetSampleRate(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
+ const auto sample_rate{impl->GetSystem().GetSampleRate()};
+
+ LOG_DEBUG(Service_Audio, "called. Sample rate {}", sample_rate);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push<u32>(renderer->GetSampleRate());
+ rb.Push(sample_rate);
}
void GetSampleCount(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
+ const auto sample_count{impl->GetSystem().GetSampleCount()};
+
+ LOG_DEBUG(Service_Audio, "called. Sample count {}", sample_count);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push<u32>(renderer->GetSampleCount());
+ rb.Push(sample_count);
}
void GetState(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
+ const u32 state{!impl->GetSystem().IsActive()};
+
+ LOG_DEBUG(Service_Audio, "called, state {}", state);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(renderer->GetStreamState()));
+ rb.Push(state);
}
void GetMixBufferCount(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
+ const auto buffer_count{impl->GetSystem().GetMixBufferCount()};
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push<u32>(renderer->GetMixBufferCount());
+ rb.Push(buffer_count);
}
- void RequestUpdateImpl(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "(STUBBED) called");
+ void RequestUpdate(Kernel::HLERequestContext& ctx) {
+ LOG_TRACE(Service_Audio, "called");
+
+ std::vector<u8> input{ctx.ReadBuffer(0)};
+
+ // These buffers are written manually to avoid an issue with WriteBuffer throwing errors for
+ // checking size 0. Performance size is 0 for most games.
+
+ std::vector<u8> output{};
+ std::vector<u8> performance{};
+ auto is_buffer_b{ctx.BufferDescriptorB()[0].Size() != 0};
+ if (is_buffer_b) {
+ const auto buffersB{ctx.BufferDescriptorB()};
+ output.resize(buffersB[0].Size(), 0);
+ performance.resize(buffersB[1].Size(), 0);
+ } else {
+ const auto buffersC{ctx.BufferDescriptorC()};
+ output.resize(buffersC[0].Size(), 0);
+ performance.resize(buffersC[1].Size(), 0);
+ }
- std::vector<u8> output_params(ctx.GetWriteBufferSize(), 0);
- auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params);
+ auto result = impl->RequestUpdate(input, performance, output);
if (result.IsSuccess()) {
- ctx.WriteBuffer(output_params);
+ if (is_buffer_b) {
+ ctx.WriteBufferB(output.data(), output.size(), 0);
+ ctx.WriteBufferB(performance.data(), performance.size(), 1);
+ } else {
+ ctx.WriteBufferC(output.data(), output.size(), 0);
+ ctx.WriteBufferC(performance.data(), performance.size(), 1);
+ }
+ } else {
+ LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.description);
}
IPC::ResponseBuilder rb{ctx, 2};
@@ -105,38 +146,45 @@ private:
}
void Start(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ LOG_DEBUG(Service_Audio, "called");
- const auto result = renderer->Start();
+ impl->Start();
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
+ rb.Push(ResultSuccess);
}
void Stop(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ LOG_DEBUG(Service_Audio, "called");
- const auto result = renderer->Stop();
+ impl->Stop();
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
+ rb.Push(ResultSuccess);
}
void QuerySystemEvent(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ LOG_DEBUG(Service_Audio, "called");
+
+ if (impl->GetSystem().GetExecutionMode() == AudioCore::ExecutionMode::Manual) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_NOT_SUPPORTED);
+ return;
+ }
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
- rb.PushCopyObjects(system_event->GetReadableEvent());
+ rb.PushCopyObjects(rendered_event->GetReadableEvent());
}
void SetRenderingTimeLimit(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "called");
+
IPC::RequestParser rp{ctx};
- rendering_time_limit_percent = rp.Pop<u32>();
- LOG_DEBUG(Service_Audio, "called. rendering_time_limit_percent={}",
- rendering_time_limit_percent);
+ auto limit = rp.PopRaw<u32>();
- ASSERT(rendering_time_limit_percent <= 100);
+ auto& system_ = impl->GetSystem();
+ system_.SetRenderingTimeLimit(limit);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -145,34 +193,34 @@ private:
void GetRenderingTimeLimit(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
+ auto& system_ = impl->GetSystem();
+ auto time = system_.GetRenderingTimeLimit();
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(rendering_time_limit_percent);
+ rb.Push(time);
}
void ExecuteAudioRendererRendering(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
-
- // This service command currently only reports an unsupported operation
- // error code, or aborts. Given that, we just always return an error
- // code in this case.
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_SUPPORTED);
}
KernelHelpers::ServiceContext service_context;
-
- Kernel::KEvent* system_event;
- std::unique_ptr<AudioCore::AudioRenderer> renderer;
- u32 rendering_time_limit_percent = 100;
+ Kernel::KEvent* rendered_event;
+ Manager& manager;
+ std::unique_ptr<Renderer> impl;
};
class IAudioDevice final : public ServiceFramework<IAudioDevice> {
+
public:
- explicit IAudioDevice(Core::System& system_, Kernel::KEvent* buffer_event_, u32_le revision_)
- : ServiceFramework{system_, "IAudioDevice"}, buffer_event{buffer_event_}, revision{
- revision_} {
+ explicit IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u32 revision,
+ u32 device_num)
+ : ServiceFramework{system_, "IAudioDevice", ServiceThreadType::CreateNew},
+ service_context{system_, "IAudioDevice"}, impl{std::make_unique<AudioDevice>(
+ system_, applet_resource_user_id,
+ revision)},
+ event{service_context.CreateEvent(fmt::format("IAudioDeviceEvent-{}", device_num))} {
static const FunctionInfo functions[] = {
{0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"},
{1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"},
@@ -186,54 +234,45 @@ public:
{10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"},
{11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"},
{12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"},
- {13, nullptr, "GetActiveAudioOutputDeviceName"},
- {14, nullptr, "ListAudioOutputDeviceName"},
+ {13, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioOutputDeviceName"},
+ {14, &IAudioDevice::ListAudioOutputDeviceName, "ListAudioOutputDeviceName"},
};
RegisterHandlers(functions);
+
+ event->GetWritableEvent().Signal();
}
-private:
- using AudioDeviceName = std::array<char, 256>;
- static constexpr std::array<std::string_view, 4> audio_device_names{{
- "AudioStereoJackOutput",
- "AudioBuiltInSpeakerOutput",
- "AudioTvOutput",
- "AudioUsbDeviceOutput",
- }};
- enum class DeviceType {
- AHUBHeadphones,
- AHUBSpeakers,
- HDA,
- USBOutput,
- };
+ ~IAudioDevice() override {
+ service_context.CloseEvent(event);
+ }
+private:
void ListAudioDeviceName(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
+ const size_t in_count = ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName);
- const bool usb_output_supported =
- IsFeatureSupported(AudioFeatures::AudioUSBDeviceOutput, revision);
- const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioDeviceName);
+ std::vector<AudioDevice::AudioDeviceName> out_names{};
- std::vector<AudioDeviceName> name_buffer;
- name_buffer.reserve(audio_device_names.size());
+ const u32 out_count = impl->ListAudioDeviceName(out_names, in_count);
- for (std::size_t i = 0; i < count && i < audio_device_names.size(); i++) {
- const auto type = static_cast<DeviceType>(i);
-
- if (!usb_output_supported && type == DeviceType::USBOutput) {
- continue;
+ std::string out{};
+ for (u32 i = 0; i < out_count; i++) {
+ std::string a{};
+ u32 j = 0;
+ while (out_names[i].name[j] != '\0') {
+ a += out_names[i].name[j];
+ j++;
}
-
- const auto& device_name = audio_device_names[i];
- auto& entry = name_buffer.emplace_back();
- device_name.copy(entry.data(), device_name.size());
+ out += "\n\t" + a;
}
- ctx.WriteBuffer(name_buffer);
+ LOG_DEBUG(Service_Audio, "called.\nNames={}", out);
IPC::ResponseBuilder rb{ctx, 3};
+
+ ctx.WriteBuffer(out_names);
+
rb.Push(ResultSuccess);
- rb.Push(static_cast<u32>(name_buffer.size()));
+ rb.Push(out_count);
}
void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
@@ -243,7 +282,11 @@ private:
const auto device_name_buffer = ctx.ReadBuffer();
const std::string name = Common::StringFromBuffer(device_name_buffer);
- LOG_WARNING(Service_Audio, "(STUBBED) called. name={}, volume={}", name, volume);
+ LOG_DEBUG(Service_Audio, "called. name={}, volume={}", name, volume);
+
+ if (name == "AudioTvOutput") {
+ impl->SetDeviceVolumes(volume);
+ }
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -253,53 +296,60 @@ private:
const auto device_name_buffer = ctx.ReadBuffer();
const std::string name = Common::StringFromBuffer(device_name_buffer);
- LOG_WARNING(Service_Audio, "(STUBBED) called. name={}", name);
+ LOG_DEBUG(Service_Audio, "called. Name={}", name);
+
+ f32 volume{1.0f};
+ if (name == "AudioTvOutput") {
+ volume = impl->GetDeviceVolume(name);
+ }
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(1.0f);
+ rb.Push(volume);
}
void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ const auto write_size = ctx.GetWriteBufferSize() / sizeof(char);
+ std::string out_name{"AudioTvOutput"};
- // Currently set to always be TV audio output.
- const auto& device_name = audio_device_names[2];
+ LOG_DEBUG(Service_Audio, "(STUBBED) called. Name={}", out_name);
- AudioDeviceName out_device_name{};
- device_name.copy(out_device_name.data(), device_name.size());
+ out_name.resize(write_size);
- ctx.WriteBuffer(out_device_name);
+ ctx.WriteBuffer(out_name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ LOG_DEBUG(Service_Audio, "(STUBBED) called");
- buffer_event->GetWritableEvent().Signal();
+ event->GetWritableEvent().Signal();
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
- rb.PushCopyObjects(buffer_event->GetReadableEvent());
+ rb.PushCopyObjects(event->GetReadableEvent());
}
void GetActiveChannelCount(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ const auto& sink{system.AudioCore().GetOutputSink()};
+ u32 channel_count{sink.GetDeviceChannels()};
+
+ LOG_DEBUG(Service_Audio, "(STUBBED) called. Channels={}", channel_count);
IPC::ResponseBuilder rb{ctx, 3};
+
rb.Push(ResultSuccess);
- rb.Push<u32>(2);
+ rb.Push<u32>(channel_count);
}
- // Should be similar to QueryAudioDeviceOutputEvent
void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ LOG_DEBUG(Service_Audio, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
- rb.PushCopyObjects(buffer_event->GetReadableEvent());
+ rb.PushCopyObjects(event->GetReadableEvent());
}
void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) {
@@ -307,402 +357,167 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
- rb.PushCopyObjects(buffer_event->GetReadableEvent());
+ rb.PushCopyObjects(event->GetReadableEvent());
}
- Kernel::KEvent* buffer_event;
- u32_le revision = 0;
+ void ListAudioOutputDeviceName(Kernel::HLERequestContext& ctx) {
+ const size_t in_count = ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName);
+
+ std::vector<AudioDevice::AudioDeviceName> out_names{};
+
+ const u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count);
+
+ std::string out{};
+ for (u32 i = 0; i < out_count; i++) {
+ std::string a{};
+ u32 j = 0;
+ while (out_names[i].name[j] != '\0') {
+ a += out_names[i].name[j];
+ j++;
+ }
+ out += "\n\t" + a;
+ }
+
+ LOG_DEBUG(Service_Audio, "called.\nNames={}", out);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+
+ ctx.WriteBuffer(out_names);
+
+ rb.Push(ResultSuccess);
+ rb.Push(out_count);
+ }
+
+ KernelHelpers::ServiceContext service_context;
+ std::unique_ptr<AudioDevice> impl;
+ Kernel::KEvent* event;
};
AudRenU::AudRenU(Core::System& system_)
- : ServiceFramework{system_, "audren:u"}, service_context{system_, "audren:u"} {
+ : ServiceFramework{system_, "audren:u", ServiceThreadType::CreateNew},
+ service_context{system_, "audren:u"}, impl{std::make_unique<Manager>(system_)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"},
- {1, &AudRenU::GetAudioRendererWorkBufferSize, "GetWorkBufferSize"},
+ {1, &AudRenU::GetWorkBufferSize, "GetWorkBufferSize"},
{2, &AudRenU::GetAudioDeviceService, "GetAudioDeviceService"},
- {3, &AudRenU::OpenAudioRendererForManualExecution, "OpenAudioRendererForManualExecution"},
+ {3, nullptr, "OpenAudioRendererForManualExecution"},
{4, &AudRenU::GetAudioDeviceServiceWithRevisionInfo, "GetAudioDeviceServiceWithRevisionInfo"},
};
// clang-format on
RegisterHandlers(functions);
-
- buffer_event = service_context.CreateEvent("IAudioOutBufferReleasedEvent");
}
-AudRenU::~AudRenU() {
- service_context.CloseEvent(buffer_event);
-}
+AudRenU::~AudRenU() = default;
void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
-
- OpenAudioRendererImpl(ctx);
-}
-
-static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) {
- // +1 represents the final mix.
- return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count +
- 1;
-}
-
-void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
-
- // Several calculations below align the sizes being calculated
- // onto a 64 byte boundary.
- static constexpr u64 buffer_alignment_size = 64;
-
- // Some calculations that calculate portions of the buffer
- // that will contain information, on the other hand, align
- // the result of some of their calcularions on a 16 byte boundary.
- static constexpr u64 info_field_alignment_size = 16;
-
- // Maximum detail entries that may exist at one time for performance
- // frame statistics.
- static constexpr u64 max_perf_detail_entries = 100;
-
- // Size of the data structure representing the bulk of the voice-related state.
- static constexpr u64 voice_state_size_bytes = 0x100;
-
- // Size of the upsampler manager data structure
- constexpr u64 upsampler_manager_size = 0x48;
-
- // Calculates the part of the size that relates to mix buffers.
- const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) {
- // As of 8.0.0 this is the maximum on voice channels.
- constexpr u64 max_voice_channels = 6;
-
- // The service expects the sample_count member of the parameters to either be
- // a value of 160 or 240, so the maximum sample count is assumed in order
- // to adequately handle all values at runtime.
- constexpr u64 default_max_sample_count = 240;
-
- const u64 total_mix_buffers = params.mix_buffer_count + max_voice_channels;
-
- u64 size = 0;
- size += total_mix_buffers * (sizeof(s32) * params.sample_count);
- size += total_mix_buffers * (sizeof(s32) * default_max_sample_count);
- size += u64{params.submix_count} + params.sink_count;
- size = Common::AlignUp(size, buffer_alignment_size);
- size += Common::AlignUp(params.unknown_30, buffer_alignment_size);
- size += Common::AlignUp(sizeof(s32) * params.mix_buffer_count, buffer_alignment_size);
- return size;
- };
-
- // Calculates the portion of the size related to the mix data (and the sorting thereof).
- const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) {
- // The size of the mixing info data structure.
- constexpr u64 mix_info_size = 0x940;
-
- // Consists of total submixes with the final mix included.
- const u64 total_mix_count = u64{params.submix_count} + 1;
-
- // The total number of effects that may be available to the audio renderer at any time.
- constexpr u64 max_effects = 256;
-
- // Calculates the part of the size related to the audio node state.
- // This will only be used if the audio revision supports the splitter.
- const auto calculate_node_state_size = [](std::size_t num_nodes) {
- // Internally within a nodestate, it appears to use a data structure
- // similar to a std::bitset<64> twice.
- constexpr u64 bit_size = Common::BitSize<u64>();
- constexpr u64 num_bitsets = 2;
-
- // Node state instances have three states internally for performing
- // depth-first searches of nodes. Initialized, Found, and Done Sorting.
- constexpr u64 num_states = 3;
-
- u64 size = 0;
- size += (num_nodes * num_nodes) * sizeof(s32);
- size += num_states * (num_nodes * sizeof(s32));
- size += num_bitsets * (Common::AlignUp(num_nodes, bit_size) / Common::BitSize<u8>());
- return size;
- };
-
- // Calculates the part of the size related to the adjacency (aka edge) matrix.
- const auto calculate_edge_matrix_size = [](std::size_t num_nodes) {
- return (num_nodes * num_nodes) * sizeof(s32);
- };
-
- u64 size = 0;
- size += Common::AlignUp(sizeof(void*) * total_mix_count, info_field_alignment_size);
- size += Common::AlignUp(mix_info_size * total_mix_count, info_field_alignment_size);
- size += Common::AlignUp(sizeof(s32) * max_effects * params.submix_count,
- info_field_alignment_size);
-
- if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
- size += Common::AlignUp(calculate_node_state_size(total_mix_count) +
- calculate_edge_matrix_size(total_mix_count),
- info_field_alignment_size);
- }
-
- return size;
- };
-
- // Calculates the part of the size related to voice channel info.
- const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) {
- constexpr u64 voice_info_size = 0x220;
- constexpr u64 voice_resource_size = 0xD0;
-
- u64 size = 0;
- size += Common::AlignUp(sizeof(void*) * params.voice_count, info_field_alignment_size);
- size += Common::AlignUp(voice_info_size * params.voice_count, info_field_alignment_size);
- size +=
- Common::AlignUp(voice_resource_size * params.voice_count, info_field_alignment_size);
- size +=
- Common::AlignUp(voice_state_size_bytes * params.voice_count, info_field_alignment_size);
- return size;
- };
-
- // Calculates the part of the size related to memory pools.
- const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) {
- const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count);
- const u64 memory_pool_info_size = 0x20;
- return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size);
- };
-
- // Calculates the part of the size related to the splitter context.
- const auto calculate_splitter_context_size =
- [](const AudioCommon::AudioRendererParameter& params) -> u64 {
- if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
- return 0;
- }
-
- constexpr u64 splitter_info_size = 0x20;
- constexpr u64 splitter_destination_data_size = 0xE0;
-
- u64 size = 0;
- size += params.num_splitter_send_channels;
- size +=
- Common::AlignUp(splitter_info_size * params.splitter_count, info_field_alignment_size);
- size += Common::AlignUp(splitter_destination_data_size * params.num_splitter_send_channels,
- info_field_alignment_size);
-
- return size;
- };
-
- // Calculates the part of the size related to the upsampler info.
- const auto calculate_upsampler_info_size =
- [](const AudioCommon::AudioRendererParameter& params) {
- constexpr u64 upsampler_info_size = 0x280;
- // Yes, using the buffer size over info alignment size is intentional here.
- return Common::AlignUp(upsampler_info_size *
- (u64{params.submix_count} + params.sink_count),
- buffer_alignment_size);
- };
-
- // Calculates the part of the size related to effect info.
- const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) {
- constexpr u64 effect_info_size = 0x2B0;
- return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size);
- };
-
- // Calculates the part of the size related to audio sink info.
- const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) {
- const u64 sink_info_size = 0x170;
- return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size);
- };
-
- // Calculates the part of the size related to voice state info.
- const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) {
- const u64 voice_state_size = 0x100;
- const u64 additional_size = buffer_alignment_size - 1;
- return Common::AlignUp(voice_state_size * params.voice_count + additional_size,
- info_field_alignment_size);
- };
-
- // Calculates the part of the size related to performance statistics.
- const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) {
- // Extra size value appended to the end of the calculation.
- constexpr u64 appended = 128;
-
- // Whether or not we assume the newer version of performance metrics data structures.
- const bool is_v2 =
- IsFeatureSupported(AudioFeatures::PerformanceMetricsVersion2, params.revision);
-
- // Data structure sizes
- constexpr u64 perf_statistics_size = 0x0C;
- const u64 header_size = is_v2 ? 0x30 : 0x18;
- const u64 entry_size = is_v2 ? 0x18 : 0x10;
- const u64 detail_size = is_v2 ? 0x18 : 0x10;
-
- const u64 entry_count = CalculateNumPerformanceEntries(params);
- const u64 size_per_frame =
- header_size + (entry_size * entry_count) + (detail_size * max_perf_detail_entries);
-
- u64 size = 0;
- size += Common::AlignUp(size_per_frame * params.performance_frame_count + 1,
- buffer_alignment_size);
- size += Common::AlignUp(perf_statistics_size, buffer_alignment_size);
- size += appended;
- return size;
- };
-
- // Calculates the part of the size that relates to the audio command buffer.
- const auto calculate_command_buffer_size =
- [](const AudioCommon::AudioRendererParameter& params) {
- constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
-
- if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
- constexpr u64 command_buffer_size = 0x18000;
-
- return command_buffer_size + alignment;
- }
-
- // When the variadic command buffer is supported, this means
- // the command generator for the audio renderer can issue commands
- // that are (as one would expect), variable in size. So what we need to do
- // is determine the maximum possible size for a few command data structures
- // then multiply them by the amount of present commands indicated by the given
- // respective audio parameters.
-
- constexpr u64 max_biquad_filters = 2;
- constexpr u64 max_mix_buffers = 24;
-
- constexpr u64 biquad_filter_command_size = 0x2C;
-
- constexpr u64 depop_mix_command_size = 0x24;
- constexpr u64 depop_setup_command_size = 0x50;
-
- constexpr u64 effect_command_max_size = 0x540;
-
- constexpr u64 mix_command_size = 0x1C;
- constexpr u64 mix_ramp_command_size = 0x24;
- constexpr u64 mix_ramp_grouped_command_size = 0x13C;
-
- constexpr u64 perf_command_size = 0x28;
-
- constexpr u64 sink_command_size = 0x130;
-
- constexpr u64 submix_command_max_size =
- depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
-
- constexpr u64 volume_command_size = 0x1C;
- constexpr u64 volume_ramp_command_size = 0x20;
-
- constexpr u64 voice_biquad_filter_command_size =
- biquad_filter_command_size * max_biquad_filters;
- constexpr u64 voice_data_command_size = 0x9C;
- const u64 voice_command_max_size =
- (params.splitter_count * depop_setup_command_size) +
- (voice_data_command_size + voice_biquad_filter_command_size +
- volume_ramp_command_size + mix_ramp_grouped_command_size);
+ IPC::RequestParser rp{ctx};
- // Now calculate the individual elements that comprise the size and add them together.
- const u64 effect_commands_size = params.effect_count * effect_command_max_size;
+ AudioCore::AudioRendererParameterInternal params;
+ rp.PopRaw<AudioCore::AudioRendererParameterInternal>(params);
+ auto transfer_memory_handle = ctx.GetCopyHandle(0);
+ auto process_handle = ctx.GetCopyHandle(1);
+ auto transfer_memory_size = rp.Pop<u64>();
+ auto applet_resource_user_id = rp.Pop<u64>();
- const u64 final_mix_commands_size =
- depop_mix_command_size + volume_command_size * max_mix_buffers;
+ if (impl->GetSessionCount() + 1 > AudioCore::MaxRendererSessions) {
+ LOG_ERROR(Service_Audio, "Too many AudioRenderer sessions open!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_MAXIMUM_SESSIONS_REACHED);
+ return;
+ }
- const u64 perf_commands_size =
- perf_command_size *
- (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
+ const auto& handle_table{system.CurrentProcess()->GetHandleTable()};
+ auto process{handle_table.GetObject<Kernel::KProcess>(process_handle)};
+ auto transfer_memory{
+ process->GetHandleTable().GetObject<Kernel::KTransferMemory>(transfer_memory_handle)};
- const u64 sink_commands_size = params.sink_count * sink_command_size;
+ const auto session_id{impl->GetSessionId()};
+ if (session_id == -1) {
+ LOG_ERROR(Service_Audio, "Tried to open a session that's already in use!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_MAXIMUM_SESSIONS_REACHED);
+ return;
+ }
- const u64 splitter_commands_size =
- params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
+ LOG_DEBUG(Service_Audio, "Opened new AudioRenderer session {} sessions open {}", session_id,
+ impl->GetSessionCount());
- const u64 submix_commands_size = params.submix_count * submix_command_max_size;
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IAudioRenderer>(system, *impl, params, transfer_memory.GetPointerUnsafe(),
+ transfer_memory_size, process_handle,
+ applet_resource_user_id, session_id);
+}
- const u64 voice_commands_size = params.voice_count * voice_command_max_size;
-
- return effect_commands_size + final_mix_commands_size + perf_commands_size +
- sink_commands_size + splitter_commands_size + submix_commands_size +
- voice_commands_size + alignment;
- };
+void AudRenU::GetWorkBufferSize(Kernel::HLERequestContext& ctx) {
+ AudioCore::AudioRendererParameterInternal params;
IPC::RequestParser rp{ctx};
- const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
-
- u64 size = 0;
- size += calculate_mix_buffer_sizes(params);
- size += calculate_mix_info_size(params);
- size += calculate_voice_info_size(params);
- size += upsampler_manager_size;
- size += calculate_memory_pools_size(params);
- size += calculate_splitter_context_size(params);
-
- size = Common::AlignUp(size, buffer_alignment_size);
-
- size += calculate_upsampler_info_size(params);
- size += calculate_effect_info_size(params);
- size += calculate_sink_info_size(params);
- size += calculate_voice_state_size(params);
- size += calculate_perf_size(params);
- size += calculate_command_buffer_size(params);
-
- // finally, 4KB page align the size, and we're done.
- size = Common::AlignUp(size, 4096);
+ rp.PopRaw<AudioCore::AudioRendererParameterInternal>(params);
+
+ u64 size{0};
+ auto result = impl->GetWorkBufferSize(params, size);
+
+ std::string output_info{};
+ output_info += fmt::format("\tRevision {}", AudioCore::GetRevisionNum(params.revision));
+ output_info +=
+ fmt::format("\n\tSample Rate {}, Sample Count {}", params.sample_rate, params.sample_count);
+ output_info += fmt::format("\n\tExecution Mode {}, Voice Drop Enabled {}",
+ static_cast<u32>(params.execution_mode), params.voice_drop_enabled);
+ output_info += fmt::format(
+ "\n\tSizes: Effects {:04X}, Mixes {:04X}, Sinks {:04X}, Submixes {:04X}, Splitter Infos "
+ "{:04X}, Splitter Destinations {:04X}, Voices {:04X}, Performance Frames {:04X} External "
+ "Context {:04X}",
+ params.effects, params.mixes, params.sinks, params.sub_mixes, params.splitter_infos,
+ params.splitter_destinations, params.voices, params.perf_frames,
+ params.external_context_size);
+
+ LOG_DEBUG(Service_Audio, "called.\nInput params:\n{}\nOutput params:\n\tWorkbuffer size {:08X}",
+ output_info, size);
IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
+ rb.Push(result);
rb.Push<u64>(size);
-
- LOG_DEBUG(Service_Audio, "buffer_size=0x{:X}", size);
}
void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const u64 aruid = rp.Pop<u64>();
- LOG_DEBUG(Service_Audio, "called. aruid={:016X}", aruid);
+ const auto applet_resource_user_id = rp.Pop<u64>();
+
+ LOG_DEBUG(Service_Audio, "called. Applet resource id {}", applet_resource_user_id);
- // Revisionless variant of GetAudioDeviceServiceWithRevisionInfo that
- // always assumes the initial release revision (REV1).
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
rb.Push(ResultSuccess);
- rb.PushIpcInterface<IAudioDevice>(system, buffer_event, Common::MakeMagic('R', 'E', 'V', '1'));
+ rb.PushIpcInterface<IAudioDevice>(system, applet_resource_user_id,
+ ::Common::MakeMagic('R', 'E', 'V', '1'), num_audio_devices++);
}
void AudRenU::OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
-
- OpenAudioRendererImpl(ctx);
}
void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx) {
struct Parameters {
u32 revision;
- u64 aruid;
+ u64 applet_resource_user_id;
};
IPC::RequestParser rp{ctx};
- const auto [revision, aruid] = rp.PopRaw<Parameters>();
- LOG_DEBUG(Service_Audio, "called. revision={:08X}, aruid={:016X}", revision, aruid);
+ const auto [revision, applet_resource_user_id] = rp.PopRaw<Parameters>();
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IAudioDevice>(system, buffer_event, revision);
-}
+ LOG_DEBUG(Service_Audio, "called. Revision {} Applet resource id {}",
+ AudioCore::GetRevisionNum(revision), applet_resource_user_id);
-void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<IAudioRenderer>(system, params, audren_instance_count++);
-}
-
-bool IsFeatureSupported(AudioFeatures feature, u32_le revision) {
- // Byte swap
- const u32_be version_num = revision - Common::MakeMagic('R', 'E', 'V', '0');
-
- switch (feature) {
- case AudioFeatures::AudioUSBDeviceOutput:
- return version_num >= 4U;
- case AudioFeatures::Splitter:
- return version_num >= 2U;
- case AudioFeatures::PerformanceMetricsVersion2:
- case AudioFeatures::VariadicCommandBuffer:
- return version_num >= 5U;
- default:
- return false;
- }
+ rb.PushIpcInterface<IAudioDevice>(system, applet_resource_user_id, revision,
+ num_audio_devices++);
}
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h
index 5922b4b27..4384a9b3c 100644
--- a/src/core/hle/service/audio/audren_u.h
+++ b/src/core/hle/service/audio/audren_u.h
@@ -1,9 +1,9 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include "audio_core/audio_render_manager.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/service.h"
@@ -16,6 +16,7 @@ class HLERequestContext;
}
namespace Service::Audio {
+class IAudioRenderer;
class AudRenU final : public ServiceFramework<AudRenU> {
public:
@@ -24,28 +25,14 @@ public:
private:
void OpenAudioRenderer(Kernel::HLERequestContext& ctx);
- void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx);
+ void GetWorkBufferSize(Kernel::HLERequestContext& ctx);
void GetAudioDeviceService(Kernel::HLERequestContext& ctx);
void OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx);
void GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx);
- void OpenAudioRendererImpl(Kernel::HLERequestContext& ctx);
-
KernelHelpers::ServiceContext service_context;
-
- std::size_t audren_instance_count = 0;
- Kernel::KEvent* buffer_event;
+ std::unique_ptr<AudioCore::AudioRenderer::Manager> impl;
+ u32 num_audio_devices{0};
};
-// Describes a particular audio feature that may be supported in a particular revision.
-enum class AudioFeatures : u32 {
- AudioUSBDeviceOutput,
- Splitter,
- PerformanceMetricsVersion2,
- VariadicCommandBuffer,
-};
-
-// Tests if a particular audio feature is supported with a given audio revision.
-bool IsFeatureSupported(AudioFeatures feature, u32_le revision);
-
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/codecctl.cpp b/src/core/hle/service/audio/codecctl.cpp
index 42961d908..81b956d7e 100644
--- a/src/core/hle/service/audio/codecctl.cpp
+++ b/src/core/hle/service/audio/codecctl.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/audio/codecctl.h"
diff --git a/src/core/hle/service/audio/codecctl.h b/src/core/hle/service/audio/codecctl.h
index 58e53259e..34da98212 100644
--- a/src/core/hle/service/audio/codecctl.h
+++ b/src/core/hle/service/audio/codecctl.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h
index 6f8c09bcf..d706978cb 100644
--- a/src/core/hle/service/audio/errors.h
+++ b/src/core/hle/service/audio/errors.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,8 +7,17 @@
namespace Service::Audio {
-constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::Audio, 2};
-constexpr ResultCode ERR_BUFFER_COUNT_EXCEEDED{ErrorModule::Audio, 8};
-constexpr ResultCode ERR_NOT_SUPPORTED{ErrorModule::Audio, 513};
+constexpr Result ERR_INVALID_DEVICE_NAME{ErrorModule::Audio, 1};
+constexpr Result ERR_OPERATION_FAILED{ErrorModule::Audio, 2};
+constexpr Result ERR_INVALID_SAMPLE_RATE{ErrorModule::Audio, 3};
+constexpr Result ERR_INSUFFICIENT_BUFFER_SIZE{ErrorModule::Audio, 4};
+constexpr Result ERR_MAXIMUM_SESSIONS_REACHED{ErrorModule::Audio, 5};
+constexpr Result ERR_BUFFER_COUNT_EXCEEDED{ErrorModule::Audio, 8};
+constexpr Result ERR_INVALID_CHANNEL_COUNT{ErrorModule::Audio, 10};
+constexpr Result ERR_INVALID_UPDATE_DATA{ErrorModule::Audio, 41};
+constexpr Result ERR_POOL_MAPPING_FAILED{ErrorModule::Audio, 42};
+constexpr Result ERR_NOT_SUPPORTED{ErrorModule::Audio, 513};
+constexpr Result ERR_INVALID_PROCESS_HANDLE{ErrorModule::Audio, 1536};
+constexpr Result ERR_INVALID_REVISION{ErrorModule::Audio, 1537};
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index 981b6c996..8bafc3a98 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <cstring>
@@ -256,6 +255,32 @@ void HwOpus::GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx) {
GetWorkBufferSize(ctx);
}
+void HwOpus::GetWorkBufferSizeForMultiStreamEx(Kernel::HLERequestContext& ctx) {
+ OpusMultiStreamParametersEx param;
+ std::memcpy(&param, ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
+
+ const auto sample_rate = param.sample_rate;
+ const auto channel_count = param.channel_count;
+ const auto number_streams = param.number_streams;
+ const auto number_stereo_streams = param.number_stereo_streams;
+
+ LOG_DEBUG(
+ Audio,
+ "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}",
+ sample_rate, channel_count, number_streams, number_stereo_streams);
+
+ ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
+ sample_rate == 12000 || sample_rate == 8000,
+ "Invalid sample rate");
+
+ const u32 worker_buffer_sz =
+ static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams));
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u32>(worker_buffer_sz);
+}
+
void HwOpus::OpenHardwareOpusDecoder(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto sample_rate = rp.Pop<u32>();
@@ -299,7 +324,7 @@ void HwOpus::OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx) {
const auto sample_rate = rp.Pop<u32>();
const auto channel_count = rp.Pop<u32>();
- LOG_CRITICAL(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count);
+ LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count);
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
sample_rate == 12000 || sample_rate == 8000,
@@ -336,7 +361,7 @@ HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {
{4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"},
{5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"},
{6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"},
- {7, nullptr, "GetWorkBufferSizeForMultiStreamEx"},
+ {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h
index b74824ff3..e6092e290 100644
--- a/src/core/hle/service/audio/hwopus.h
+++ b/src/core/hle/service/audio/hwopus.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -12,6 +11,16 @@ class System;
namespace Service::Audio {
+struct OpusMultiStreamParametersEx {
+ u32 sample_rate;
+ u32 channel_count;
+ u32 number_streams;
+ u32 number_stereo_streams;
+ u32 use_large_frame_size;
+ u32 padding;
+ std::array<u32, 64> channel_mappings;
+};
+
class HwOpus final : public ServiceFramework<HwOpus> {
public:
explicit HwOpus(Core::System& system_);
@@ -22,6 +31,7 @@ private:
void OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx);
void GetWorkBufferSize(Kernel::HLERequestContext& ctx);
void GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx);
+ void GetWorkBufferSizeForMultiStreamEx(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Audio
diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp
index ee49edbb9..cd0b405ff 100644
--- a/src/core/hle/service/bcat/backend/backend.cpp
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/hex_util.h"
#include "common/logging/log.h"
@@ -75,7 +74,7 @@ void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
SignalUpdate();
}
-void ProgressServiceBackend::FinishDownload(ResultCode result) {
+void ProgressServiceBackend::FinishDownload(Result result) {
impl.total_downloaded_bytes = impl.total_bytes;
impl.status = DeliveryCacheProgressImpl::Status::Done;
impl.result = result;
diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h
index 63833c927..205ed0702 100644
--- a/src/core/hle/service/bcat/backend/backend.h
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -50,7 +49,7 @@ struct DeliveryCacheProgressImpl {
};
Status status;
- ResultCode result = ResultSuccess;
+ Result result = ResultSuccess;
DirectoryName current_directory;
FileName current_file;
s64 current_downloaded_bytes; ///< Bytes downloaded on current file.
@@ -91,7 +90,7 @@ public:
void CommitDirectory(std::string_view dir_name);
// Notifies the application that the operation completed with result code result.
- void FinishDownload(ResultCode result);
+ void FinishDownload(Result result);
private:
explicit ProgressServiceBackend(Core::System& system, std::string_view event_name);
diff --git a/src/core/hle/service/bcat/bcat.cpp b/src/core/hle/service/bcat/bcat.cpp
index 5a95707de..d0ac17324 100644
--- a/src/core/hle/service/bcat/bcat.cpp
+++ b/src/core/hle/service/bcat/bcat.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/bcat/bcat.h"
diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h
index 1eba477da..db9d3c8c5 100644
--- a/src/core/hle/service/bcat/bcat.h
+++ b/src/core/hle/service/bcat/bcat.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/bcat/bcat_module.cpp b/src/core/hle/service/bcat/bcat_module.cpp
index 500e7e52d..bc08ac487 100644
--- a/src/core/hle/service/bcat/bcat_module.cpp
+++ b/src/core/hle/service/bcat/bcat_module.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cctype>
#include <mbedtls/md5.h>
@@ -19,15 +18,15 @@
namespace Service::BCAT {
-constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1};
-constexpr ResultCode ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2};
-constexpr ResultCode ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6};
-constexpr ResultCode ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7};
+constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1};
+constexpr Result ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2};
+constexpr Result ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6};
+constexpr Result ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7};
// The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files
// and if any of them have a non-zero result it just forwards that result. This is the FS error code
// for permission denied, which is the closest approximation of this scenario.
-constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400};
+constexpr Result ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400};
using BCATDigest = std::array<u8, 0x10>;
@@ -141,8 +140,8 @@ public:
{20401, nullptr, "UnregisterSystemApplicationDeliveryTask"},
{20410, nullptr, "SetSystemApplicationDeliveryTaskTimer"},
{30100, &IBcatService::SetPassphrase, "SetPassphrase"},
- {30101, nullptr, "Unknown"},
- {30102, nullptr, "Unknown2"},
+ {30101, nullptr, "Unknown30101"},
+ {30102, nullptr, "Unknown30102"},
{30200, nullptr, "RegisterBackgroundDeliveryTask"},
{30201, nullptr, "UnregisterBackgroundDeliveryTask"},
{30202, nullptr, "BlockDeliveryTask"},
diff --git a/src/core/hle/service/bcat/bcat_module.h b/src/core/hle/service/bcat/bcat_module.h
index 738731c06..b2fcf9bfb 100644
--- a/src/core/hle/service/bcat/bcat_module.h
+++ b/src/core/hle/service/bcat/bcat_module.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/bpc/bpc.cpp b/src/core/hle/service/bpc/bpc.cpp
index 78e01d8d8..466163538 100644
--- a/src/core/hle/service/bpc/bpc.cpp
+++ b/src/core/hle/service/bpc/bpc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
diff --git a/src/core/hle/service/bpc/bpc.h b/src/core/hle/service/bpc/bpc.h
index 6ec25aa9b..8adc2f962 100644
--- a/src/core/hle/service/bpc/bpc.h
+++ b/src/core/hle/service/bpc/bpc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/btdrv/btdrv.cpp b/src/core/hle/service/btdrv/btdrv.cpp
index 0787f43f4..ec7e5320c 100644
--- a/src/core/hle/service/btdrv/btdrv.cpp
+++ b/src/core/hle/service/btdrv/btdrv.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/core.h"
@@ -182,6 +181,11 @@ public:
{147, nullptr, "RegisterAudioControlNotification"},
{148, nullptr, "SendAudioControlPassthroughCommand"},
{149, nullptr, "SendAudioControlSetAbsoluteVolumeCommand"},
+ {150, nullptr, "AcquireAudioSinkVolumeLocallyChangedEvent"},
+ {151, nullptr, "AcquireAudioSinkVolumeUpdateRequestCompletedEvent"},
+ {152, nullptr, "GetAudioSinkVolume"},
+ {153, nullptr, "RequestUpdateAudioSinkVolume"},
+ {154, nullptr, "IsAudioSinkVolumeSupported"},
{256, nullptr, "IsManufacturingMode"},
{257, nullptr, "EmulateBluetoothCrash"},
{258, nullptr, "GetBleChannelMap"},
diff --git a/src/core/hle/service/btdrv/btdrv.h b/src/core/hle/service/btdrv/btdrv.h
index 191410dbc..9cbe2926f 100644
--- a/src/core/hle/service/btdrv/btdrv.h
+++ b/src/core/hle/service/btdrv/btdrv.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp
index cc268d877..eebf85e03 100644
--- a/src/core/hle/service/btm/btm.cpp
+++ b/src/core/hle/service/btm/btm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
@@ -215,8 +214,12 @@ public:
{76, nullptr, "Unknown76"},
{100, nullptr, "Unknown100"},
{101, nullptr, "Unknown101"},
- {110, nullptr, "Unknown102"},
- {111, nullptr, "Unknown103"},
+ {110, nullptr, "Unknown110"},
+ {111, nullptr, "Unknown111"},
+ {112, nullptr, "Unknown112"},
+ {113, nullptr, "Unknown113"},
+ {114, nullptr, "Unknown114"},
+ {115, nullptr, "Unknown115"},
};
// clang-format on
diff --git a/src/core/hle/service/btm/btm.h b/src/core/hle/service/btm/btm.h
index c6b878043..9dcda1848 100644
--- a/src/core/hle/service/btm/btm.h
+++ b/src/core/hle/service/btm/btm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp
index 5b7fe8e9b..13940a8c9 100644
--- a/src/core/hle/service/caps/caps.cpp
+++ b/src/core/hle/service/caps/caps.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/caps/caps.h"
#include "core/hle/service/caps/caps_a.h"
diff --git a/src/core/hle/service/caps/caps.h b/src/core/hle/service/caps/caps.h
index 7254055e6..3e89c82cb 100644
--- a/src/core/hle/service/caps/caps.h
+++ b/src/core/hle/service/caps/caps.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp
index 6220e9f77..44267b284 100644
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/caps/caps_a.h"
diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h
index 389cc6dbe..319c173d8 100644
--- a/src/core/hle/service/caps/caps_a.h
+++ b/src/core/hle/service/caps/caps_a.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/caps/caps_c.cpp b/src/core/hle/service/caps/caps_c.cpp
index a9ad5c8b1..725a2e3a7 100644
--- a/src/core/hle/service/caps/caps_c.cpp
+++ b/src/core/hle/service/caps/caps_c.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
diff --git a/src/core/hle/service/caps/caps_c.h b/src/core/hle/service/caps/caps_c.h
index c6d1dfdce..983a4212d 100644
--- a/src/core/hle/service/caps/caps_c.h
+++ b/src/core/hle/service/caps/caps_c.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/caps/caps_sc.cpp b/src/core/hle/service/caps/caps_sc.cpp
index d91e18e80..395b13da7 100644
--- a/src/core/hle/service/caps/caps_sc.cpp
+++ b/src/core/hle/service/caps/caps_sc.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/caps/caps_sc.h"
diff --git a/src/core/hle/service/caps/caps_sc.h b/src/core/hle/service/caps/caps_sc.h
index e79a33ee5..e5600f6d7 100644
--- a/src/core/hle/service/caps/caps_sc.h
+++ b/src/core/hle/service/caps/caps_sc.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp
index 33a976ddf..62b9edd41 100644
--- a/src/core/hle/service/caps/caps_ss.cpp
+++ b/src/core/hle/service/caps/caps_ss.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/caps/caps_ss.h"
diff --git a/src/core/hle/service/caps/caps_ss.h b/src/core/hle/service/caps/caps_ss.h
index 1816f7885..718ade485 100644
--- a/src/core/hle/service/caps/caps_ss.h
+++ b/src/core/hle/service/caps/caps_ss.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp
index 45b705950..fcb496756 100644
--- a/src/core/hle/service/caps/caps_su.cpp
+++ b/src/core/hle/service/caps/caps_su.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h
index b366fdb13..c9a1d507b 100644
--- a/src/core/hle/service/caps/caps_su.h
+++ b/src/core/hle/service/caps/caps_su.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp
index 8f8ee2bb4..5fbba8673 100644
--- a/src/core/hle/service/caps/caps_u.cpp
+++ b/src/core/hle/service/caps/caps_u.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
diff --git a/src/core/hle/service/caps/caps_u.h b/src/core/hle/service/caps/caps_u.h
index e7e0d8775..c3d4b9cea 100644
--- a/src/core/hle/service/caps/caps_u.h
+++ b/src/core/hle/service/caps/caps_u.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/erpt/erpt.cpp b/src/core/hle/service/erpt/erpt.cpp
index c767926a4..923c0022a 100644
--- a/src/core/hle/service/erpt/erpt.cpp
+++ b/src/core/hle/service/erpt/erpt.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
diff --git a/src/core/hle/service/erpt/erpt.h b/src/core/hle/service/erpt/erpt.h
index 8cd5c081f..507d626ec 100644
--- a/src/core/hle/service/erpt/erpt.h
+++ b/src/core/hle/service/erpt/erpt.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp
index f6184acc9..ff9b0427c 100644
--- a/src/core/hle/service/es/es.cpp
+++ b/src/core/hle/service/es/es.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/crypto/key_manager.h"
#include "core/hle/ipc_helpers.h"
@@ -9,8 +8,8 @@
namespace Service::ES {
-constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::ETicket, 2};
-constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3};
+constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::ETicket, 2};
+constexpr Result ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3};
class ETicket final : public ServiceFramework<ETicket> {
public:
diff --git a/src/core/hle/service/es/es.h b/src/core/hle/service/es/es.h
index 2a7b27d12..530563550 100644
--- a/src/core/hle/service/es/es.h
+++ b/src/core/hle/service/es/es.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/eupld/eupld.cpp b/src/core/hle/service/eupld/eupld.cpp
index 2d650b1b7..d1553ace0 100644
--- a/src/core/hle/service/eupld/eupld.cpp
+++ b/src/core/hle/service/eupld/eupld.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
diff --git a/src/core/hle/service/eupld/eupld.h b/src/core/hle/service/eupld/eupld.h
index 539993a9d..5de8219be 100644
--- a/src/core/hle/service/eupld/eupld.h
+++ b/src/core/hle/service/eupld/eupld.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
index f84506af0..27675615b 100644
--- a/src/core/hle/service/fatal/fatal.cpp
+++ b/src/core/hle/service/fatal/fatal.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <cstring>
@@ -63,8 +62,7 @@ enum class FatalType : u32 {
ErrorScreen = 2,
};
-static void GenerateErrorReport(Core::System& system, ResultCode error_code,
- const FatalInfo& info) {
+static void GenerateErrorReport(Core::System& system, Result error_code, const FatalInfo& info) {
const auto title_id = system.GetCurrentProcessProgramID();
std::string crash_report = fmt::format(
"Yuzu {}-{} crash report\n"
@@ -107,7 +105,7 @@ static void GenerateErrorReport(Core::System& system, ResultCode error_code,
info.backtrace_size, info.ArchAsString(), info.unk10);
}
-static void ThrowFatalError(Core::System& system, ResultCode error_code, FatalType fatal_type,
+static void ThrowFatalError(Core::System& system, Result error_code, FatalType fatal_type,
const FatalInfo& info) {
LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code 0x{:X}", fatal_type,
error_code.raw);
@@ -130,7 +128,7 @@ static void ThrowFatalError(Core::System& system, ResultCode error_code, FatalTy
void Module::Interface::ThrowFatal(Kernel::HLERequestContext& ctx) {
LOG_ERROR(Service_Fatal, "called");
IPC::RequestParser rp{ctx};
- const auto error_code = rp.Pop<ResultCode>();
+ const auto error_code = rp.Pop<Result>();
ThrowFatalError(system, error_code, FatalType::ErrorScreen, {});
IPC::ResponseBuilder rb{ctx, 2};
@@ -140,7 +138,7 @@ void Module::Interface::ThrowFatal(Kernel::HLERequestContext& ctx) {
void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) {
LOG_ERROR(Service_Fatal, "called");
IPC::RequestParser rp(ctx);
- const auto error_code = rp.Pop<ResultCode>();
+ const auto error_code = rp.Pop<Result>();
const auto fatal_type = rp.PopEnum<FatalType>();
ThrowFatalError(system, error_code, fatal_type,
@@ -152,7 +150,7 @@ void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) {
void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) {
LOG_ERROR(Service_Fatal, "called");
IPC::RequestParser rp(ctx);
- const auto error_code = rp.Pop<ResultCode>();
+ const auto error_code = rp.Pop<Result>();
const auto fatal_type = rp.PopEnum<FatalType>();
const auto fatal_info = ctx.ReadBuffer();
FatalInfo info{};
diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h
index 2095bf89f..a7a310f7b 100644
--- a/src/core/hle/service/fatal/fatal.h
+++ b/src/core/hle/service/fatal/fatal.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/fatal/fatal_p.cpp b/src/core/hle/service/fatal/fatal_p.cpp
index 8672b85dc..4a81bb5e2 100644
--- a/src/core/hle/service/fatal/fatal_p.cpp
+++ b/src/core/hle/service/fatal/fatal_p.cpp
@@ -1,13 +1,18 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/fatal/fatal_p.h"
namespace Service::Fatal {
Fatal_P::Fatal_P(std::shared_ptr<Module> module_, Core::System& system_)
- : Interface(std::move(module_), system_, "fatal:p") {}
+ : Interface(std::move(module_), system_, "fatal:p") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetFatalEvent"},
+ {10, nullptr, "GetFatalContext"},
+ };
+ RegisterHandlers(functions);
+}
Fatal_P::~Fatal_P() = default;
diff --git a/src/core/hle/service/fatal/fatal_p.h b/src/core/hle/service/fatal/fatal_p.h
index ffa5b7b98..f74336835 100644
--- a/src/core/hle/service/fatal/fatal_p.h
+++ b/src/core/hle/service/fatal/fatal_p.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp
index 82993938a..3739711fc 100644
--- a/src/core/hle/service/fatal/fatal_u.cpp
+++ b/src/core/hle/service/fatal/fatal_u.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/fatal/fatal_u.h"
diff --git a/src/core/hle/service/fatal/fatal_u.h b/src/core/hle/service/fatal/fatal_u.h
index 0b58c9112..65fbe2696 100644
--- a/src/core/hle/service/fatal/fatal_u.h
+++ b/src/core/hle/service/fatal/fatal_u.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/fgm/fgm.cpp b/src/core/hle/service/fgm/fgm.cpp
index d7a638f96..7e9fb0385 100644
--- a/src/core/hle/service/fgm/fgm.cpp
+++ b/src/core/hle/service/fgm/fgm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
diff --git a/src/core/hle/service/fgm/fgm.h b/src/core/hle/service/fgm/fgm.h
index 75978f2ed..077e48812 100644
--- a/src/core/hle/service/fgm/fgm.h
+++ b/src/core/hle/service/fgm/fgm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 3703ca4c6..11c604a0f 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <utility>
@@ -50,7 +49,7 @@ std::string VfsDirectoryServiceWrapper::GetName() const {
return backing->GetName();
}
-ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size) const {
+Result VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size) const {
std::string path(Common::FS::SanitizePath(path_));
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
if (dir == nullptr) {
@@ -74,7 +73,7 @@ ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64
return ResultSuccess;
}
-ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const {
+Result VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const {
std::string path(Common::FS::SanitizePath(path_));
if (path.empty()) {
// TODO(DarkLordZach): Why do games call this and what should it do? Works as is but...
@@ -93,7 +92,7 @@ ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) cons
return ResultSuccess;
}
-ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const {
+Result VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const {
std::string path(Common::FS::SanitizePath(path_));
// NOTE: This is inaccurate behavior. CreateDirectory is not recursive.
@@ -117,7 +116,7 @@ ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_)
return ResultSuccess;
}
-ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) const {
+Result VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) const {
std::string path(Common::FS::SanitizePath(path_));
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
if (!dir->DeleteSubdirectory(Common::FS::GetFilename(path))) {
@@ -127,7 +126,7 @@ ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_)
return ResultSuccess;
}
-ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path_) const {
+Result VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path_) const {
std::string path(Common::FS::SanitizePath(path_));
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
if (!dir->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path))) {
@@ -137,7 +136,7 @@ ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::str
return ResultSuccess;
}
-ResultCode VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::string& path) const {
+Result VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::string& path) const {
const std::string sanitized_path(Common::FS::SanitizePath(path));
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(sanitized_path));
@@ -149,8 +148,8 @@ ResultCode VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::stri
return ResultSuccess;
}
-ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
- const std::string& dest_path_) const {
+Result VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
+ const std::string& dest_path_) const {
std::string src_path(Common::FS::SanitizePath(src_path_));
std::string dest_path(Common::FS::SanitizePath(dest_path_));
auto src = backing->GetFileRelative(src_path);
@@ -174,7 +173,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
@@ -184,8 +183,8 @@ ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
return ResultSuccess;
}
-ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_,
- const std::string& dest_path_) const {
+Result VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_,
+ const std::string& dest_path_) const {
std::string src_path(Common::FS::SanitizePath(src_path_));
std::string dest_path(Common::FS::SanitizePath(dest_path_));
auto src = GetDirectoryRelativeWrapped(backing, src_path);
@@ -274,28 +273,27 @@ FileSystemController::FileSystemController(Core::System& system_) : system{syste
FileSystemController::~FileSystemController() = default;
-ResultCode FileSystemController::RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
+Result FileSystemController::RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
romfs_factory = std::move(factory);
LOG_DEBUG(Service_FS, "Registered RomFS");
return ResultSuccess;
}
-ResultCode FileSystemController::RegisterSaveData(
- std::unique_ptr<FileSys::SaveDataFactory>&& factory) {
+Result FileSystemController::RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory) {
ASSERT_MSG(save_data_factory == nullptr, "Tried to register a second save data");
save_data_factory = std::move(factory);
LOG_DEBUG(Service_FS, "Registered save data");
return ResultSuccess;
}
-ResultCode FileSystemController::RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
+Result FileSystemController::RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
ASSERT_MSG(sdmc_factory == nullptr, "Tried to register a second SDMC");
sdmc_factory = std::move(factory);
LOG_DEBUG(Service_FS, "Registered SDMC");
return ResultSuccess;
}
-ResultCode FileSystemController::RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
+Result FileSystemController::RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS");
bis_factory = std::move(factory);
LOG_DEBUG(Service_FS, "Registered BIS");
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index b155e0811..5b27de9fa 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -59,10 +58,10 @@ public:
explicit FileSystemController(Core::System& system_);
~FileSystemController();
- ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
- ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory);
- ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
- ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
+ Result RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
+ Result RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory);
+ Result RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
+ Result RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
void SetPackedUpdate(FileSys::VirtualFile update_raw);
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() const;
@@ -142,7 +141,7 @@ private:
void InstallInterfaces(Core::System& system);
-// A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of
+// A class that wraps a VfsDirectory with methods that return ResultVal and Result instead of
// pointers and booleans. This makes using a VfsDirectory with switch services much easier and
// avoids repetitive code.
class VfsDirectoryServiceWrapper {
@@ -161,35 +160,35 @@ public:
* @param size The size of the new file, filled with zeroes
* @return Result of the operation
*/
- ResultCode CreateFile(const std::string& path, u64 size) const;
+ Result CreateFile(const std::string& path, u64 size) const;
/**
* Delete a file specified by its path
* @param path Path relative to the archive
* @return Result of the operation
*/
- ResultCode DeleteFile(const std::string& path) const;
+ Result DeleteFile(const std::string& path) const;
/**
* Create a directory specified by its path
* @param path Path relative to the archive
* @return Result of the operation
*/
- ResultCode CreateDirectory(const std::string& path) const;
+ Result CreateDirectory(const std::string& path) const;
/**
* Delete a directory specified by its path
* @param path Path relative to the archive
* @return Result of the operation
*/
- ResultCode DeleteDirectory(const std::string& path) const;
+ Result DeleteDirectory(const std::string& path) const;
/**
* Delete a directory specified by its path and anything under it
* @param path Path relative to the archive
* @return Result of the operation
*/
- ResultCode DeleteDirectoryRecursively(const std::string& path) const;
+ Result DeleteDirectoryRecursively(const std::string& path) const;
/**
* Cleans the specified directory. This is similar to DeleteDirectoryRecursively,
@@ -201,7 +200,7 @@ public:
*
* @return Result of the operation.
*/
- ResultCode CleanDirectoryRecursively(const std::string& path) const;
+ Result CleanDirectoryRecursively(const std::string& path) const;
/**
* Rename a File specified by its path
@@ -209,7 +208,7 @@ public:
* @param dest_path Destination path relative to the archive
* @return Result of the operation
*/
- ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const;
+ Result RenameFile(const std::string& src_path, const std::string& dest_path) const;
/**
* Rename a Directory specified by its path
@@ -217,7 +216,7 @@ public:
* @param dest_path Destination path relative to the archive
* @return Result of the operation
*/
- ResultCode RenameDirectory(const std::string& src_path, const std::string& dest_path) const;
+ Result RenameDirectory(const std::string& src_path, const std::string& dest_path) const;
/**
* Open a file specified by its path, using the specified mode
diff --git a/src/core/hle/service/filesystem/fsp_ldr.cpp b/src/core/hle/service/filesystem/fsp_ldr.cpp
index f112ae9d0..1e3366e71 100644
--- a/src/core/hle/service/filesystem/fsp_ldr.cpp
+++ b/src/core/hle/service/filesystem/fsp_ldr.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/filesystem/fsp_ldr.h"
diff --git a/src/core/hle/service/filesystem/fsp_ldr.h b/src/core/hle/service/filesystem/fsp_ldr.h
index d6432a0e1..358739a87 100644
--- a/src/core/hle/service/filesystem/fsp_ldr.h
+++ b/src/core/hle/service/filesystem/fsp_ldr.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/filesystem/fsp_pr.cpp b/src/core/hle/service/filesystem/fsp_pr.cpp
index 9b7f7d861..4ffc31977 100644
--- a/src/core/hle/service/filesystem/fsp_pr.cpp
+++ b/src/core/hle/service/filesystem/fsp_pr.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/filesystem/fsp_pr.h"
diff --git a/src/core/hle/service/filesystem/fsp_pr.h b/src/core/hle/service/filesystem/fsp_pr.h
index 9e622518c..bd4e0a730 100644
--- a/src/core/hle/service/filesystem/fsp_pr.h
+++ b/src/core/hle/service/filesystem/fsp_pr.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index b087e7bba..e23eae36a 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cinttypes>
#include <cstring>
@@ -58,7 +57,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 +116,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"},
@@ -245,14 +246,16 @@ static void BuildEntryIndex(std::vector<FileSys::Entry>& entries, const std::vec
entries.reserve(entries.size() + new_data.size());
for (const auto& new_entry : new_data) {
- entries.emplace_back(new_entry->GetName(), type, new_entry->GetSize());
+ entries.emplace_back(new_entry->GetName(), type,
+ type == FileSys::EntryType::Directory ? 0 : new_entry->GetSize());
}
}
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"},
@@ -897,7 +900,7 @@ void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
case FileSys::SaveDataSpaceId::TemporaryStorage:
case FileSys::SaveDataSpaceId::ProperSystem:
case FileSys::SaveDataSpaceId::SafeMode:
- UNREACHABLE();
+ ASSERT(false);
}
auto filesystem = std::make_shared<IFileSystem>(system, std::move(dir.Unwrap()),
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index 556708284..36f552e05 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/friend/errors.h b/src/core/hle/service/friend/errors.h
index b3996e275..ff525d865 100644
--- a/src/core/hle/service/friend/errors.h
+++ b/src/core/hle/service/friend/errors.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,5 +7,5 @@
namespace Service::Friend {
-constexpr ResultCode ERR_NO_NOTIFICATIONS{ErrorModule::Account, 15};
+constexpr Result ERR_NO_NOTIFICATIONS{ErrorModule::Account, 15};
}
diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp
index 79cd3acbb..e0db787fc 100644
--- a/src/core/hle/service/friend/friend.cpp
+++ b/src/core/hle/service/friend/friend.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <queue>
#include "common/logging/log.h"
diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h
index 8be3321db..444da8b35 100644
--- a/src/core/hle/service/friend/friend.h
+++ b/src/core/hle/service/friend/friend.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/friend/friend_interface.cpp b/src/core/hle/service/friend/friend_interface.cpp
index 9b18b2a32..c8b98b1f0 100644
--- a/src/core/hle/service/friend/friend_interface.cpp
+++ b/src/core/hle/service/friend/friend_interface.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/friend/friend_interface.h"
diff --git a/src/core/hle/service/friend/friend_interface.h b/src/core/hle/service/friend/friend_interface.h
index 43d914b32..3a2184c34 100644
--- a/src/core/hle/service/friend/friend_interface.h
+++ b/src/core/hle/service/friend/friend_interface.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp
index 2feead2aa..49b6d45fe 100644
--- a/src/core/hle/service/glue/arp.cpp
+++ b/src/core/hle/service/glue/arp.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
@@ -154,7 +153,7 @@ class IRegistrar final : public ServiceFramework<IRegistrar> {
friend class ARP_W;
public:
- using IssuerFn = std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)>;
+ using IssuerFn = std::function<Result(u64, ApplicationLaunchProperty, std::vector<u8>)>;
explicit IRegistrar(Core::System& system_, IssuerFn&& issuer)
: ServiceFramework{system_, "IRegistrar"}, issue_process_id{std::move(issuer)} {
diff --git a/src/core/hle/service/glue/arp.h b/src/core/hle/service/glue/arp.h
index 0df3c5e1f..06c992e88 100644
--- a/src/core/hle/service/glue/arp.h
+++ b/src/core/hle/service/glue/arp.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/glue/bgtc.cpp b/src/core/hle/service/glue/bgtc.cpp
index 564c3b750..3248091c3 100644
--- a/src/core/hle/service/glue/bgtc.cpp
+++ b/src/core/hle/service/glue/bgtc.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/core.h"
diff --git a/src/core/hle/service/glue/bgtc.h b/src/core/hle/service/glue/bgtc.h
index 4c0142fd5..d6e2baec1 100644
--- a/src/core/hle/service/glue/bgtc.h
+++ b/src/core/hle/service/glue/bgtc.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/glue/ectx.cpp b/src/core/hle/service/glue/ectx.cpp
index 249c6f003..1bd9314ae 100644
--- a/src/core/hle/service/glue/ectx.cpp
+++ b/src/core/hle/service/glue/ectx.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/glue/ectx.h"
diff --git a/src/core/hle/service/glue/ectx.h b/src/core/hle/service/glue/ectx.h
index b275e808a..a608de053 100644
--- a/src/core/hle/service/glue/ectx.h
+++ b/src/core/hle/service/glue/ectx.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/glue/errors.h b/src/core/hle/service/glue/errors.h
index f6647f724..d4ce7f44e 100644
--- a/src/core/hle/service/glue/errors.h
+++ b/src/core/hle/service/glue/errors.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,9 +7,9 @@
namespace Service::Glue {
-constexpr ResultCode ERR_INVALID_RESOURCE{ErrorModule::ARP, 30};
-constexpr ResultCode ERR_INVALID_PROCESS_ID{ErrorModule::ARP, 31};
-constexpr ResultCode ERR_INVALID_ACCESS{ErrorModule::ARP, 42};
-constexpr ResultCode ERR_NOT_REGISTERED{ErrorModule::ARP, 102};
+constexpr Result ERR_INVALID_RESOURCE{ErrorModule::ARP, 30};
+constexpr Result ERR_INVALID_PROCESS_ID{ErrorModule::ARP, 31};
+constexpr Result ERR_INVALID_ACCESS{ErrorModule::ARP, 42};
+constexpr Result ERR_NOT_REGISTERED{ErrorModule::ARP, 102};
} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/glue.cpp b/src/core/hle/service/glue/glue.cpp
index b24d469cf..717f2562b 100644
--- a/src/core/hle/service/glue/glue.cpp
+++ b/src/core/hle/service/glue/glue.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include "core/core.h"
diff --git a/src/core/hle/service/glue/glue.h b/src/core/hle/service/glue/glue.h
index 112cd238b..ae7c6d235 100644
--- a/src/core/hle/service/glue/glue.h
+++ b/src/core/hle/service/glue/glue.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/glue/glue_manager.cpp b/src/core/hle/service/glue/glue_manager.cpp
index 48e133b48..8a654cdca 100644
--- a/src/core/hle/service/glue/glue_manager.cpp
+++ b/src/core/hle/service/glue/glue_manager.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/glue/errors.h"
#include "core/hle/service/glue/glue_manager.h"
@@ -42,8 +41,8 @@ ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const {
return iter->second.control;
}
-ResultCode ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch,
- std::vector<u8> control) {
+Result ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch,
+ std::vector<u8> control) {
if (title_id == 0) {
return ERR_INVALID_PROCESS_ID;
}
@@ -57,7 +56,7 @@ ResultCode ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch,
return ResultSuccess;
}
-ResultCode ARPManager::Unregister(u64 title_id) {
+Result ARPManager::Unregister(u64 title_id) {
if (title_id == 0) {
return ERR_INVALID_PROCESS_ID;
}
diff --git a/src/core/hle/service/glue/glue_manager.h b/src/core/hle/service/glue/glue_manager.h
index 4bc5297c6..cd0b092ac 100644
--- a/src/core/hle/service/glue/glue_manager.h
+++ b/src/core/hle/service/glue/glue_manager.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -43,12 +42,12 @@ public:
// Adds a new entry to the internal database with the provided parameters, returning
// ERR_INVALID_ACCESS if attempting to re-register a title ID without an intermediate Unregister
// step, and ERR_INVALID_PROCESS_ID if the title ID is 0.
- ResultCode Register(u64 title_id, ApplicationLaunchProperty launch, std::vector<u8> control);
+ Result Register(u64 title_id, ApplicationLaunchProperty launch, std::vector<u8> control);
// Removes the registration for the provided title ID from the database, returning
// ERR_NOT_REGISTERED if it doesn't exist in the database and ERR_INVALID_PROCESS_ID if the
// title ID is 0.
- ResultCode Unregister(u64 title_id);
+ Result Unregister(u64 title_id);
// Removes all entries from the database, always succeeds. Should only be used when resetting
// system state.
diff --git a/src/core/hle/service/glue/notif.cpp b/src/core/hle/service/glue/notif.cpp
index c559ec9df..3ace2dabd 100644
--- a/src/core/hle/service/glue/notif.cpp
+++ b/src/core/hle/service/glue/notif.cpp
@@ -1,7 +1,11 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <algorithm>
+#include <cstring>
+
+#include "common/assert.h"
+#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/glue/notif.h"
@@ -10,11 +14,11 @@ namespace Service::Glue {
NOTIF_A::NOTIF_A(Core::System& system_) : ServiceFramework{system_, "notif:a"} {
// clang-format off
static const FunctionInfo functions[] = {
- {500, nullptr, "RegisterAlarmSetting"},
- {510, nullptr, "UpdateAlarmSetting"},
+ {500, &NOTIF_A::RegisterAlarmSetting, "RegisterAlarmSetting"},
+ {510, &NOTIF_A::UpdateAlarmSetting, "UpdateAlarmSetting"},
{520, &NOTIF_A::ListAlarmSettings, "ListAlarmSettings"},
- {530, nullptr, "LoadApplicationParameter"},
- {540, nullptr, "DeleteAlarmSetting"},
+ {530, &NOTIF_A::LoadApplicationParameter, "LoadApplicationParameter"},
+ {540, &NOTIF_A::DeleteAlarmSetting, "DeleteAlarmSetting"},
{1000, &NOTIF_A::Initialize, "Initialize"},
};
// clang-format on
@@ -24,21 +28,132 @@ NOTIF_A::NOTIF_A(Core::System& system_) : ServiceFramework{system_, "notif:a"} {
NOTIF_A::~NOTIF_A() = default;
+void NOTIF_A::RegisterAlarmSetting(Kernel::HLERequestContext& ctx) {
+ const auto alarm_setting_buffer_size = ctx.GetReadBufferSize(0);
+ const auto application_parameter_size = ctx.GetReadBufferSize(1);
+
+ ASSERT_MSG(alarm_setting_buffer_size == sizeof(AlarmSetting),
+ "alarm_setting_buffer_size is not 0x40 bytes");
+ ASSERT_MSG(application_parameter_size <= sizeof(ApplicationParameter),
+ "application_parameter_size is bigger than 0x400 bytes");
+
+ AlarmSetting new_alarm{};
+ memcpy(&new_alarm, ctx.ReadBuffer(0).data(), sizeof(AlarmSetting));
+
+ // TODO: Count alarms per game id
+ if (alarms.size() >= max_alarms) {
+ LOG_ERROR(Service_NOTIF, "Alarm limit reached");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ new_alarm.alarm_setting_id = last_alarm_setting_id++;
+ alarms.push_back(new_alarm);
+
+ // TODO: Save application parameter data
+
+ LOG_WARNING(Service_NOTIF,
+ "(STUBBED) called, application_parameter_size={}, setting_id={}, kind={}, muted={}",
+ application_parameter_size, new_alarm.alarm_setting_id, new_alarm.kind,
+ new_alarm.muted);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ rb.Push(new_alarm.alarm_setting_id);
+}
+
+void NOTIF_A::UpdateAlarmSetting(Kernel::HLERequestContext& ctx) {
+ const auto alarm_setting_buffer_size = ctx.GetReadBufferSize(0);
+ const auto application_parameter_size = ctx.GetReadBufferSize(1);
+
+ ASSERT_MSG(alarm_setting_buffer_size == sizeof(AlarmSetting),
+ "alarm_setting_buffer_size is not 0x40 bytes");
+ ASSERT_MSG(application_parameter_size <= sizeof(ApplicationParameter),
+ "application_parameter_size is bigger than 0x400 bytes");
+
+ AlarmSetting alarm_setting{};
+ memcpy(&alarm_setting, ctx.ReadBuffer(0).data(), sizeof(AlarmSetting));
+
+ const auto alarm_it = GetAlarmFromId(alarm_setting.alarm_setting_id);
+ if (alarm_it != alarms.end()) {
+ LOG_DEBUG(Service_NOTIF, "Alarm updated");
+ *alarm_it = alarm_setting;
+ // TODO: Save application parameter data
+ }
+
+ LOG_WARNING(Service_NOTIF,
+ "(STUBBED) called, application_parameter_size={}, setting_id={}, kind={}, muted={}",
+ application_parameter_size, alarm_setting.alarm_setting_id, alarm_setting.kind,
+ alarm_setting.muted);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void NOTIF_A::ListAlarmSettings(Kernel::HLERequestContext& ctx) {
- // Returns an array of AlarmSetting
- constexpr s32 alarm_count = 0;
+ LOG_INFO(Service_NOTIF, "called, alarm_count={}", alarms.size());
- LOG_WARNING(Service_NOTIF, "(STUBBED) called");
+ // TODO: Only return alarms of this game id
+ ctx.WriteBuffer(alarms);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(alarm_count);
+ rb.Push(static_cast<u32>(alarms.size()));
+}
+
+void NOTIF_A::LoadApplicationParameter(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto alarm_setting_id{rp.Pop<AlarmSettingId>()};
+
+ const auto alarm_it = GetAlarmFromId(alarm_setting_id);
+ if (alarm_it == alarms.end()) {
+ LOG_ERROR(Service_NOTIF, "Invalid alarm setting id={}", alarm_setting_id);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ // TODO: Read application parameter related to this setting id
+ ApplicationParameter application_parameter{};
+
+ LOG_WARNING(Service_NOTIF, "(STUBBED) called, alarm_setting_id={}", alarm_setting_id);
+
+ ctx.WriteBuffer(application_parameter);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(application_parameter.size()));
+}
+
+void NOTIF_A::DeleteAlarmSetting(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto alarm_setting_id{rp.Pop<AlarmSettingId>()};
+
+ std::erase_if(alarms, [alarm_setting_id](const AlarmSetting& alarm) {
+ return alarm.alarm_setting_id == alarm_setting_id;
+ });
+
+ LOG_INFO(Service_NOTIF, "called, alarm_setting_id={}", alarm_setting_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
}
void NOTIF_A::Initialize(Kernel::HLERequestContext& ctx) {
+ // TODO: Load previous alarms from config
+
LOG_WARNING(Service_NOTIF, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
+std::vector<NOTIF_A::AlarmSetting>::iterator NOTIF_A::GetAlarmFromId(
+ AlarmSettingId alarm_setting_id) {
+ return std::find_if(alarms.begin(), alarms.end(),
+ [alarm_setting_id](const AlarmSetting& alarm) {
+ return alarm.alarm_setting_id == alarm_setting_id;
+ });
+}
+
} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/notif.h b/src/core/hle/service/glue/notif.h
index 6ecf2015c..4467e1f35 100644
--- a/src/core/hle/service/glue/notif.h
+++ b/src/core/hle/service/glue/notif.h
@@ -1,9 +1,12 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include <array>
+#include <vector>
+
+#include "common/uuid.h"
#include "core/hle/service/service.h"
namespace Core {
@@ -18,8 +21,52 @@ public:
~NOTIF_A() override;
private:
+ static constexpr std::size_t max_alarms = 8;
+
+ // This is nn::notification::AlarmSettingId
+ using AlarmSettingId = u16;
+ static_assert(sizeof(AlarmSettingId) == 0x2, "AlarmSettingId is an invalid size");
+
+ using ApplicationParameter = std::array<u8, 0x400>;
+ static_assert(sizeof(ApplicationParameter) == 0x400, "ApplicationParameter is an invalid size");
+
+ struct DailyAlarmSetting {
+ s8 hour;
+ s8 minute;
+ };
+ static_assert(sizeof(DailyAlarmSetting) == 0x2, "DailyAlarmSetting is an invalid size");
+
+ struct WeeklyScheduleAlarmSetting {
+ INSERT_PADDING_BYTES(0xA);
+ std::array<DailyAlarmSetting, 0x7> day_of_week;
+ };
+ static_assert(sizeof(WeeklyScheduleAlarmSetting) == 0x18,
+ "WeeklyScheduleAlarmSetting is an invalid size");
+
+ // This is nn::notification::AlarmSetting
+ struct AlarmSetting {
+ AlarmSettingId alarm_setting_id;
+ u8 kind;
+ u8 muted;
+ INSERT_PADDING_BYTES(0x4);
+ Common::UUID account_id;
+ u64 application_id;
+ INSERT_PADDING_BYTES(0x8);
+ WeeklyScheduleAlarmSetting schedule;
+ };
+ static_assert(sizeof(AlarmSetting) == 0x40, "AlarmSetting is an invalid size");
+
+ void RegisterAlarmSetting(Kernel::HLERequestContext& ctx);
+ void UpdateAlarmSetting(Kernel::HLERequestContext& ctx);
void ListAlarmSettings(Kernel::HLERequestContext& ctx);
+ void LoadApplicationParameter(Kernel::HLERequestContext& ctx);
+ void DeleteAlarmSetting(Kernel::HLERequestContext& ctx);
void Initialize(Kernel::HLERequestContext& ctx);
+
+ std::vector<AlarmSetting>::iterator GetAlarmFromId(AlarmSettingId alarm_setting_id);
+
+ std::vector<AlarmSetting> alarms{};
+ AlarmSettingId last_alarm_setting_id{};
};
} // namespace Service::Glue
diff --git a/src/core/hle/service/grc/grc.cpp b/src/core/hle/service/grc/grc.cpp
index f918bdf03..4b684f6d0 100644
--- a/src/core/hle/service/grc/grc.cpp
+++ b/src/core/hle/service/grc/grc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
diff --git a/src/core/hle/service/grc/grc.h b/src/core/hle/service/grc/grc.h
index 9069fe756..f8c2f8dab 100644
--- a/src/core/hle/service/grc/grc.h
+++ b/src/core/hle/service/grc/grc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/hid/controllers/console_sixaxis.cpp b/src/core/hle/service/hid/controllers/console_sixaxis.cpp
index a727b3582..bb3cba910 100644
--- a/src/core/hle/service/hid/controllers/console_sixaxis.cpp
+++ b/src/core/hle/service/hid/controllers/console_sixaxis.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include "common/settings.h"
#include "core/core_timing.h"
#include "core/hid/emulated_console.h"
#include "core/hid/hid_core.h"
@@ -11,9 +9,14 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C200;
-Controller_ConsoleSixAxis::Controller_ConsoleSixAxis(Core::HID::HIDCore& hid_core_)
+Controller_ConsoleSixAxis::Controller_ConsoleSixAxis(Core::HID::HIDCore& hid_core_,
+ u8* raw_shared_memory_)
: ControllerBase{hid_core_} {
console = hid_core.GetEmulatedConsole();
+ static_assert(SHARED_MEMORY_OFFSET + sizeof(ConsoleSharedMemory) < shared_memory_size,
+ "ConsoleSharedMemory is bigger than the shared memory");
+ shared_memory = std::construct_at(
+ reinterpret_cast<ConsoleSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET));
}
Controller_ConsoleSixAxis::~Controller_ConsoleSixAxis() = default;
@@ -22,8 +25,7 @@ void Controller_ConsoleSixAxis::OnInit() {}
void Controller_ConsoleSixAxis::OnRelease() {}
-void Controller_ConsoleSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
- std::size_t size) {
+void Controller_ConsoleSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated() || !is_transfer_memory_set) {
seven_sixaxis_lifo.buffer_count = 0;
seven_sixaxis_lifo.buffer_tail = 0;
@@ -50,13 +52,11 @@ void Controller_ConsoleSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_ti
-motion_status.quaternion.xyz.z,
};
- console_six_axis.sampling_number++;
- console_six_axis.is_seven_six_axis_sensor_at_rest = motion_status.is_at_rest;
- console_six_axis.verticalization_error = motion_status.verticalization_error;
- console_six_axis.gyro_bias = motion_status.gyro_bias;
+ shared_memory->sampling_number++;
+ shared_memory->is_seven_six_axis_sensor_at_rest = motion_status.is_at_rest;
+ shared_memory->verticalization_error = motion_status.verticalization_error;
+ shared_memory->gyro_bias = motion_status.gyro_bias;
- // Update console six axis shared memory
- std::memcpy(data + SHARED_MEMORY_OFFSET, &console_six_axis, sizeof(console_six_axis));
// Update seven six axis transfer memory
seven_sixaxis_lifo.WriteNextEntry(next_seven_sixaxis_state);
std::memcpy(transfer_memory, &seven_sixaxis_lifo, sizeof(seven_sixaxis_lifo));
diff --git a/src/core/hle/service/hid/controllers/console_sixaxis.h b/src/core/hle/service/hid/controllers/console_sixaxis.h
index 26d153f0c..2fd11538f 100644
--- a/src/core/hle/service/hid/controllers/console_sixaxis.h
+++ b/src/core/hle/service/hid/controllers/console_sixaxis.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,7 +7,6 @@
#include "common/common_types.h"
#include "common/quaternion.h"
-#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
@@ -19,7 +17,7 @@ class EmulatedConsole;
namespace Service::HID {
class Controller_ConsoleSixAxis final : public ControllerBase {
public:
- explicit Controller_ConsoleSixAxis(Core::HID::HIDCore& hid_core_);
+ explicit Controller_ConsoleSixAxis(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
~Controller_ConsoleSixAxis() override;
// Called when the controller is initialized
@@ -29,7 +27,7 @@ public:
void OnRelease() override;
// When the controller is requesting an update for the shared memory
- void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, size_t size) override;
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
// Called on InitializeSevenSixAxisSensor
void SetTransferMemoryPointer(u8* t_mem);
@@ -63,12 +61,13 @@ private:
Lifo<SevenSixAxisState, 0x21> seven_sixaxis_lifo{};
static_assert(sizeof(seven_sixaxis_lifo) == 0xA70, "SevenSixAxisState is an invalid size");
- Core::HID::EmulatedConsole* console;
+ SevenSixAxisState next_seven_sixaxis_state{};
u8* transfer_memory = nullptr;
+ ConsoleSharedMemory* shared_memory = nullptr;
+ Core::HID::EmulatedConsole* console = nullptr;
+
bool is_transfer_memory_set = false;
u64 last_saved_timestamp{};
u64 last_global_timestamp{};
- ConsoleSharedMemory console_six_axis{};
- SevenSixAxisState next_seven_sixaxis_state{};
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/controller_base.cpp b/src/core/hle/service/hid/controllers/controller_base.cpp
index 788ae9ae7..c58d67d7d 100644
--- a/src/core/hle/service/hid/controllers/controller_base.cpp
+++ b/src/core/hle/service/hid/controllers/controller_base.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/hid/controllers/controller_base.h"
diff --git a/src/core/hle/service/hid/controllers/controller_base.h b/src/core/hle/service/hid/controllers/controller_base.h
index 7450eb20a..d6f7a5073 100644
--- a/src/core/hle/service/hid/controllers/controller_base.h
+++ b/src/core/hle/service/hid/controllers/controller_base.h
@@ -1,11 +1,9 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
-#include "common/swap.h"
namespace Core::Timing {
class CoreTiming;
@@ -28,12 +26,10 @@ public:
virtual void OnRelease() = 0;
// When the controller is requesting an update for the shared memory
- virtual void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
- std::size_t size) = 0;
+ virtual void OnUpdate(const Core::Timing::CoreTiming& core_timing) = 0;
// When the controller is requesting a motion update for the shared memory
- virtual void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
- std::size_t size) {}
+ virtual void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing) {}
void ActivateController();
@@ -42,6 +38,7 @@ public:
bool IsControllerActivated() const;
static const std::size_t hid_entry_count = 17;
+ static const std::size_t shared_memory_size = 0x40000;
protected:
bool is_activated{false};
diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp
index 6a6fb9cab..8ec9f4a95 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.cpp
+++ b/src/core/hle/service/hid/controllers/debug_pad.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/common_types.h"
@@ -14,8 +13,12 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x00000;
-Controller_DebugPad::Controller_DebugPad(Core::HID::HIDCore& hid_core_)
+Controller_DebugPad::Controller_DebugPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
: ControllerBase{hid_core_} {
+ static_assert(SHARED_MEMORY_OFFSET + sizeof(DebugPadSharedMemory) < shared_memory_size,
+ "DebugPadSharedMemory is bigger than the shared memory");
+ shared_memory = std::construct_at(
+ reinterpret_cast<DebugPadSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET));
controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other);
}
@@ -25,16 +28,14 @@ void Controller_DebugPad::OnInit() {}
void Controller_DebugPad::OnRelease() {}
-void Controller_DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
- std::size_t size) {
+void Controller_DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
- debug_pad_lifo.buffer_count = 0;
- debug_pad_lifo.buffer_tail = 0;
- std::memcpy(data + SHARED_MEMORY_OFFSET, &debug_pad_lifo, sizeof(debug_pad_lifo));
+ shared_memory->debug_pad_lifo.buffer_count = 0;
+ shared_memory->debug_pad_lifo.buffer_tail = 0;
return;
}
- const auto& last_entry = debug_pad_lifo.ReadCurrentEntry().state;
+ const auto& last_entry = shared_memory->debug_pad_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
if (Settings::values.debug_pad_enabled) {
@@ -48,8 +49,7 @@ void Controller_DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing,
next_state.r_stick = stick_state.right;
}
- debug_pad_lifo.WriteNextEntry(next_state);
- std::memcpy(data + SHARED_MEMORY_OFFSET, &debug_pad_lifo, sizeof(debug_pad_lifo));
+ shared_memory->debug_pad_lifo.WriteNextEntry(next_state);
}
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h
index afe374fc2..68ff0ea79 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.h
+++ b/src/core/hle/service/hid/controllers/debug_pad.h
@@ -1,14 +1,10 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <array>
#include "common/bit_field.h"
-#include "common/common_funcs.h"
#include "common/common_types.h"
-#include "common/swap.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
@@ -21,7 +17,7 @@ struct AnalogStickState;
namespace Service::HID {
class Controller_DebugPad final : public ControllerBase {
public:
- explicit Controller_DebugPad(Core::HID::HIDCore& hid_core_);
+ explicit Controller_DebugPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
~Controller_DebugPad() override;
// Called when the controller is initialized
@@ -31,7 +27,7 @@ public:
void OnRelease() override;
// When the controller is requesting an update for the shared memory
- void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
private:
// This is nn::hid::DebugPadAttribute
@@ -45,19 +41,24 @@ private:
// This is nn::hid::DebugPadState
struct DebugPadState {
- s64 sampling_number;
- DebugPadAttribute attribute;
- Core::HID::DebugPadButton pad_state;
- Core::HID::AnalogStickState r_stick;
- Core::HID::AnalogStickState l_stick;
+ s64 sampling_number{};
+ DebugPadAttribute attribute{};
+ Core::HID::DebugPadButton pad_state{};
+ Core::HID::AnalogStickState r_stick{};
+ Core::HID::AnalogStickState l_stick{};
};
static_assert(sizeof(DebugPadState) == 0x20, "DebugPadState is an invalid state");
- // This is nn::hid::detail::DebugPadLifo
- Lifo<DebugPadState, hid_entry_count> debug_pad_lifo{};
- static_assert(sizeof(debug_pad_lifo) == 0x2C8, "debug_pad_lifo is an invalid size");
- DebugPadState next_state{};
+ struct DebugPadSharedMemory {
+ // This is nn::hid::detail::DebugPadLifo
+ Lifo<DebugPadState, hid_entry_count> debug_pad_lifo{};
+ static_assert(sizeof(debug_pad_lifo) == 0x2C8, "debug_pad_lifo is an invalid size");
+ INSERT_PADDING_WORDS(0x4E);
+ };
+ static_assert(sizeof(DebugPadSharedMemory) == 0x400, "DebugPadSharedMemory is an invalid size");
- Core::HID::EmulatedController* controller;
+ DebugPadState next_state{};
+ DebugPadSharedMemory* shared_memory = nullptr;
+ Core::HID::EmulatedController* controller = nullptr;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp
index fe895c4f6..32e0708ba 100644
--- a/src/core/hle/service/hid/controllers/gesture.cpp
+++ b/src/core/hle/service/hid/controllers/gesture.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "common/math_util.h"
@@ -24,25 +23,28 @@ constexpr f32 Square(s32 num) {
return static_cast<f32>(num * num);
}
-Controller_Gesture::Controller_Gesture(Core::HID::HIDCore& hid_core_) : ControllerBase(hid_core_) {
+Controller_Gesture::Controller_Gesture(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
+ : ControllerBase(hid_core_) {
+ static_assert(SHARED_MEMORY_OFFSET + sizeof(GestureSharedMemory) < shared_memory_size,
+ "GestureSharedMemory is bigger than the shared memory");
+ shared_memory = std::construct_at(
+ reinterpret_cast<GestureSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET));
console = hid_core.GetEmulatedConsole();
}
Controller_Gesture::~Controller_Gesture() = default;
void Controller_Gesture::OnInit() {
- gesture_lifo.buffer_count = 0;
- gesture_lifo.buffer_tail = 0;
+ shared_memory->gesture_lifo.buffer_count = 0;
+ shared_memory->gesture_lifo.buffer_tail = 0;
force_update = true;
}
void Controller_Gesture::OnRelease() {}
-void Controller_Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
- std::size_t size) {
+void Controller_Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
- gesture_lifo.buffer_count = 0;
- gesture_lifo.buffer_tail = 0;
- std::memcpy(data + SHARED_MEMORY_OFFSET, &gesture_lifo, sizeof(gesture_lifo));
+ shared_memory->gesture_lifo.buffer_count = 0;
+ shared_memory->gesture_lifo.buffer_tail = 0;
return;
}
@@ -50,15 +52,16 @@ void Controller_Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing, u
GestureProperties gesture = GetGestureProperties();
f32 time_difference =
- static_cast<f32>(gesture_lifo.timestamp - last_update_timestamp) / (1000 * 1000 * 1000);
+ static_cast<f32>(shared_memory->gesture_lifo.timestamp - last_update_timestamp) /
+ (1000 * 1000 * 1000);
// Only update if necesary
if (!ShouldUpdateGesture(gesture, time_difference)) {
return;
}
- last_update_timestamp = gesture_lifo.timestamp;
- UpdateGestureSharedMemory(data, size, gesture, time_difference);
+ last_update_timestamp = shared_memory->gesture_lifo.timestamp;
+ UpdateGestureSharedMemory(gesture, time_difference);
}
void Controller_Gesture::ReadTouchInput() {
@@ -92,13 +95,12 @@ bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture,
return false;
}
-void Controller_Gesture::UpdateGestureSharedMemory(u8* data, std::size_t size,
- GestureProperties& gesture,
+void Controller_Gesture::UpdateGestureSharedMemory(GestureProperties& gesture,
f32 time_difference) {
GestureType type = GestureType::Idle;
GestureAttribute attributes{};
- const auto& last_entry = gesture_lifo.ReadCurrentEntry().state;
+ const auto& last_entry = shared_memory->gesture_lifo.ReadCurrentEntry().state;
// Reset next state to default
next_state.sampling_number = last_entry.sampling_number + 1;
@@ -128,8 +130,7 @@ void Controller_Gesture::UpdateGestureSharedMemory(u8* data, std::size_t size,
next_state.points = gesture.points;
last_gesture = gesture;
- gesture_lifo.WriteNextEntry(next_state);
- std::memcpy(data + SHARED_MEMORY_OFFSET, &gesture_lifo, sizeof(gesture_lifo));
+ shared_memory->gesture_lifo.WriteNextEntry(next_state);
}
void Controller_Gesture::NewGesture(GestureProperties& gesture, GestureType& type,
@@ -306,7 +307,7 @@ void Controller_Gesture::SetSwipeEvent(GestureProperties& gesture,
}
const Controller_Gesture::GestureState& Controller_Gesture::GetLastGestureEntry() const {
- return gesture_lifo.ReadCurrentEntry().state;
+ return shared_memory->gesture_lifo.ReadCurrentEntry().state;
}
Controller_Gesture::GestureProperties Controller_Gesture::GetGestureProperties() {
diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h
index 0936a3fa3..0d6099ea0 100644
--- a/src/core/hle/service/hid/controllers/gesture.h
+++ b/src/core/hle/service/hid/controllers/gesture.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -15,7 +14,7 @@
namespace Service::HID {
class Controller_Gesture final : public ControllerBase {
public:
- explicit Controller_Gesture(Core::HID::HIDCore& hid_core_);
+ explicit Controller_Gesture(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
~Controller_Gesture() override;
// Called when the controller is initialized
@@ -25,7 +24,7 @@ public:
void OnRelease() override;
// When the controller is requesting an update for the shared memory
- void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, size_t size) override;
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
private:
static constexpr size_t MAX_FINGERS = 16;
@@ -67,19 +66,19 @@ private:
// This is nn::hid::GestureState
struct GestureState {
- s64 sampling_number;
- s64 detection_count;
- GestureType type;
- GestureDirection direction;
- Common::Point<s32> pos;
- Common::Point<s32> delta;
- f32 vel_x;
- f32 vel_y;
- GestureAttribute attributes;
- f32 scale;
- f32 rotation_angle;
- s32 point_count;
- std::array<Common::Point<s32>, 4> points;
+ s64 sampling_number{};
+ s64 detection_count{};
+ GestureType type{GestureType::Idle};
+ GestureDirection direction{GestureDirection::None};
+ Common::Point<s32> pos{};
+ Common::Point<s32> delta{};
+ f32 vel_x{};
+ f32 vel_y{};
+ GestureAttribute attributes{};
+ f32 scale{};
+ f32 rotation_angle{};
+ s32 point_count{};
+ std::array<Common::Point<s32>, 4> points{};
};
static_assert(sizeof(GestureState) == 0x60, "GestureState is an invalid size");
@@ -93,6 +92,14 @@ private:
f32 angle{};
};
+ struct GestureSharedMemory {
+ // This is nn::hid::detail::GestureLifo
+ Lifo<GestureState, hid_entry_count> gesture_lifo{};
+ static_assert(sizeof(gesture_lifo) == 0x708, "gesture_lifo is an invalid size");
+ INSERT_PADDING_WORDS(0x3E);
+ };
+ static_assert(sizeof(GestureSharedMemory) == 0x800, "GestureSharedMemory is an invalid size");
+
// Reads input from all available input engines
void ReadTouchInput();
@@ -100,8 +107,7 @@ private:
bool ShouldUpdateGesture(const GestureProperties& gesture, f32 time_difference);
// Updates the shared memory to the next state
- void UpdateGestureSharedMemory(u8* data, std::size_t size, GestureProperties& gesture,
- f32 time_difference);
+ void UpdateGestureSharedMemory(GestureProperties& gesture, f32 time_difference);
// Initializes new gesture
void NewGesture(GestureProperties& gesture, GestureType& type, GestureAttribute& attributes);
@@ -135,12 +141,9 @@ private:
// Returns the average distance, angle and middle point of the active fingers
GestureProperties GetGestureProperties();
- // This is nn::hid::detail::GestureLifo
- Lifo<GestureState, hid_entry_count> gesture_lifo{};
- static_assert(sizeof(gesture_lifo) == 0x708, "gesture_lifo is an invalid size");
GestureState next_state{};
-
- Core::HID::EmulatedConsole* console;
+ GestureSharedMemory* shared_memory = nullptr;
+ Core::HID::EmulatedConsole* console = nullptr;
std::array<Core::HID::TouchFinger, MAX_POINTS> fingers{};
GestureProperties last_gesture{};
diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp
index 9588a6910..117d87433 100644
--- a/src/core/hle/service/hid/controllers/keyboard.cpp
+++ b/src/core/hle/service/hid/controllers/keyboard.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/common_types.h"
@@ -13,8 +12,12 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800;
-Controller_Keyboard::Controller_Keyboard(Core::HID::HIDCore& hid_core_)
+Controller_Keyboard::Controller_Keyboard(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
: ControllerBase{hid_core_} {
+ static_assert(SHARED_MEMORY_OFFSET + sizeof(KeyboardSharedMemory) < shared_memory_size,
+ "KeyboardSharedMemory is bigger than the shared memory");
+ shared_memory = std::construct_at(
+ reinterpret_cast<KeyboardSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET));
emulated_devices = hid_core.GetEmulatedDevices();
}
@@ -24,16 +27,14 @@ void Controller_Keyboard::OnInit() {}
void Controller_Keyboard::OnRelease() {}
-void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
- std::size_t size) {
+void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
- keyboard_lifo.buffer_count = 0;
- keyboard_lifo.buffer_tail = 0;
- std::memcpy(data + SHARED_MEMORY_OFFSET, &keyboard_lifo, sizeof(keyboard_lifo));
+ shared_memory->keyboard_lifo.buffer_count = 0;
+ shared_memory->keyboard_lifo.buffer_tail = 0;
return;
}
- const auto& last_entry = keyboard_lifo.ReadCurrentEntry().state;
+ const auto& last_entry = shared_memory->keyboard_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
if (Settings::values.keyboard_enabled) {
@@ -45,8 +46,7 @@ void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing,
next_state.attribute.is_connected.Assign(1);
}
- keyboard_lifo.WriteNextEntry(next_state);
- std::memcpy(data + SHARED_MEMORY_OFFSET, &keyboard_lifo, sizeof(keyboard_lifo));
+ shared_memory->keyboard_lifo.WriteNextEntry(next_state);
}
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h
index cf62d3896..7532f53c6 100644
--- a/src/core/hle/service/hid/controllers/keyboard.h
+++ b/src/core/hle/service/hid/controllers/keyboard.h
@@ -1,14 +1,9 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <array>
-#include "common/bit_field.h"
-#include "common/common_funcs.h"
#include "common/common_types.h"
-#include "common/swap.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
@@ -21,7 +16,7 @@ struct KeyboardKey;
namespace Service::HID {
class Controller_Keyboard final : public ControllerBase {
public:
- explicit Controller_Keyboard(Core::HID::HIDCore& hid_core_);
+ explicit Controller_Keyboard(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
~Controller_Keyboard() override;
// Called when the controller is initialized
@@ -31,23 +26,28 @@ public:
void OnRelease() override;
// When the controller is requesting an update for the shared memory
- void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
private:
// This is nn::hid::detail::KeyboardState
struct KeyboardState {
- s64 sampling_number;
- Core::HID::KeyboardModifier modifier;
- Core::HID::KeyboardAttribute attribute;
- Core::HID::KeyboardKey key;
+ s64 sampling_number{};
+ Core::HID::KeyboardModifier modifier{};
+ Core::HID::KeyboardAttribute attribute{};
+ Core::HID::KeyboardKey key{};
};
static_assert(sizeof(KeyboardState) == 0x30, "KeyboardState is an invalid size");
- // This is nn::hid::detail::KeyboardLifo
- Lifo<KeyboardState, hid_entry_count> keyboard_lifo{};
- static_assert(sizeof(keyboard_lifo) == 0x3D8, "keyboard_lifo is an invalid size");
- KeyboardState next_state{};
+ struct KeyboardSharedMemory {
+ // This is nn::hid::detail::KeyboardLifo
+ Lifo<KeyboardState, hid_entry_count> keyboard_lifo{};
+ static_assert(sizeof(keyboard_lifo) == 0x3D8, "keyboard_lifo is an invalid size");
+ INSERT_PADDING_WORDS(0xA);
+ };
+ static_assert(sizeof(KeyboardSharedMemory) == 0x400, "KeyboardSharedMemory is an invalid size");
- Core::HID::EmulatedDevices* emulated_devices;
+ KeyboardState next_state{};
+ KeyboardSharedMemory* shared_memory = nullptr;
+ Core::HID::EmulatedDevices* emulated_devices = nullptr;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp
index ba79888ae..b11cb438d 100644
--- a/src/core/hle/service/hid/controllers/mouse.cpp
+++ b/src/core/hle/service/hid/controllers/mouse.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/common_types.h"
@@ -13,7 +12,12 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400;
-Controller_Mouse::Controller_Mouse(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} {
+Controller_Mouse::Controller_Mouse(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
+ : ControllerBase{hid_core_} {
+ static_assert(SHARED_MEMORY_OFFSET + sizeof(MouseSharedMemory) < shared_memory_size,
+ "MouseSharedMemory is bigger than the shared memory");
+ shared_memory = std::construct_at(
+ reinterpret_cast<MouseSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET));
emulated_devices = hid_core.GetEmulatedDevices();
}
@@ -22,16 +26,14 @@ Controller_Mouse::~Controller_Mouse() = default;
void Controller_Mouse::OnInit() {}
void Controller_Mouse::OnRelease() {}
-void Controller_Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
- std::size_t size) {
+void Controller_Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
- mouse_lifo.buffer_count = 0;
- mouse_lifo.buffer_tail = 0;
- std::memcpy(data + SHARED_MEMORY_OFFSET, &mouse_lifo, sizeof(mouse_lifo));
+ shared_memory->mouse_lifo.buffer_count = 0;
+ shared_memory->mouse_lifo.buffer_tail = 0;
return;
}
- const auto& last_entry = mouse_lifo.ReadCurrentEntry().state;
+ const auto& last_entry = shared_memory->mouse_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
next_state.attribute.raw = 0;
@@ -51,8 +53,7 @@ void Controller_Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
next_state.button = mouse_button_state;
}
- mouse_lifo.WriteNextEntry(next_state);
- std::memcpy(data + SHARED_MEMORY_OFFSET, &mouse_lifo, sizeof(mouse_lifo));
+ shared_memory->mouse_lifo.WriteNextEntry(next_state);
}
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h
index 7559fc78d..733d00577 100644
--- a/src/core/hle/service/hid/controllers/mouse.h
+++ b/src/core/hle/service/hid/controllers/mouse.h
@@ -1,13 +1,9 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <array>
-#include "common/bit_field.h"
#include "common/common_types.h"
-#include "common/swap.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
@@ -20,7 +16,7 @@ struct AnalogStickState;
namespace Service::HID {
class Controller_Mouse final : public ControllerBase {
public:
- explicit Controller_Mouse(Core::HID::HIDCore& hid_core_);
+ explicit Controller_Mouse(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
~Controller_Mouse() override;
// Called when the controller is initialized
@@ -30,15 +26,20 @@ public:
void OnRelease() override;
// When the controller is requesting an update for the shared memory
- void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
private:
- // This is nn::hid::detail::MouseLifo
- Lifo<Core::HID::MouseState, hid_entry_count> mouse_lifo{};
- static_assert(sizeof(mouse_lifo) == 0x350, "mouse_lifo is an invalid size");
- Core::HID::MouseState next_state{};
+ struct MouseSharedMemory {
+ // This is nn::hid::detail::MouseLifo
+ Lifo<Core::HID::MouseState, hid_entry_count> mouse_lifo{};
+ static_assert(sizeof(mouse_lifo) == 0x350, "mouse_lifo is an invalid size");
+ INSERT_PADDING_WORDS(0x2C);
+ };
+ static_assert(sizeof(MouseSharedMemory) == 0x400, "MouseSharedMemory is an invalid size");
- Core::HID::AnalogStickState last_mouse_wheel_state;
- Core::HID::EmulatedDevices* emulated_devices;
+ Core::HID::MouseState next_state{};
+ Core::HID::AnalogStickState last_mouse_wheel_state{};
+ MouseSharedMemory* shared_memory = nullptr;
+ Core::HID::EmulatedDevices* emulated_devices = nullptr;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index e5c951e06..f8972ec7a 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -1,10 +1,11 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
+#include <chrono>
#include <cstring>
+
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/common_types.h"
@@ -17,6 +18,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 {
@@ -47,23 +49,52 @@ 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;
+Result Controller_NPad::IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle) {
+ 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;
+
+ if (!npad_type) {
+ return VibrationInvalidStyleIndex;
+ }
+ if (!npad_id) {
+ return VibrationInvalidNpadId;
+ }
+ if (!device_index) {
+ return VibrationDeviceIndexOutOfRange;
+ }
+
+ return ResultSuccess;
}
-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;
+Result Controller_NPad::VerifyValidSixAxisSensorHandle(
+ const Core::HID::SixAxisSensorHandle& device_handle) {
+ const auto npad_id = IsNpadIdValid(static_cast<Core::HID::NpadIdType>(device_handle.npad_id));
+ const bool device_index = device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex;
+ const bool npad_type = device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType;
+
+ if (!npad_id) {
+ return InvalidNpadId;
+ }
+ if (!device_index) {
+ return NpadDeviceIndexOutOfRange;
+ }
+ // This doesn't get validated on nnsdk
+ if (!npad_type) {
+ return NpadInvalidHandle;
+ }
+
+ return ResultSuccess;
}
-Controller_NPad::Controller_NPad(Core::HID::HIDCore& hid_core_,
+Controller_NPad::Controller_NPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
KernelHelpers::ServiceContext& service_context_)
: ControllerBase{hid_core_}, service_context{service_context_} {
+ static_assert(NPAD_OFFSET + (NPAD_COUNT * sizeof(NpadInternalState)) < shared_memory_size);
for (std::size_t i = 0; i < controller_data.size(); ++i) {
auto& controller = controller_data[i];
+ controller.shared_memory = std::construct_at(reinterpret_cast<NpadInternalState*>(
+ raw_shared_memory_ + NPAD_OFFSET + (i * sizeof(NpadInternalState))));
controller.device = hid_core.GetEmulatedControllerByIndex(i);
controller.vibration[Core::HID::EmulatedDeviceIndex::LeftIndex].latest_vibration_value =
Core::HID::DEFAULT_VIBRATION_VALUE;
@@ -113,11 +144,11 @@ void Controller_NPad::ControllerUpdate(Core::HID::ControllerTriggerType type,
if (!controller.device->IsConnected()) {
return;
}
- auto& shared_memory = controller.shared_memory_entry;
+ auto* shared_memory = controller.shared_memory;
const auto& battery_level = controller.device->GetBattery();
- shared_memory.battery_level_dual = battery_level.dual.battery_level;
- shared_memory.battery_level_left = battery_level.left.battery_level;
- shared_memory.battery_level_right = battery_level.right.battery_level;
+ shared_memory->battery_level_dual = battery_level.dual.battery_level;
+ shared_memory->battery_level_left = battery_level.left.battery_level;
+ shared_memory->battery_level_right = battery_level.right.battery_level;
break;
}
default:
@@ -132,123 +163,178 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
}
LOG_DEBUG(Service_HID, "Npad connected {}", npad_id);
const auto controller_type = controller.device->GetNpadStyleIndex();
- auto& shared_memory = controller.shared_memory_entry;
+ const auto& body_colors = controller.device->GetColors();
+ const auto& battery_level = controller.device->GetBattery();
+ auto* shared_memory = controller.shared_memory;
if (controller_type == Core::HID::NpadStyleIndex::None) {
controller.styleset_changed_event->GetWritableEvent().Signal();
return;
}
- shared_memory.style_tag.raw = Core::HID::NpadStyleSet::None;
- shared_memory.device_type.raw = 0;
- shared_memory.system_properties.raw = 0;
+
+ // Reset memory values
+ shared_memory->style_tag.raw = Core::HID::NpadStyleSet::None;
+ shared_memory->device_type.raw = 0;
+ shared_memory->system_properties.raw = 0;
+ shared_memory->joycon_color.attribute = ColorAttribute::NoController;
+ shared_memory->joycon_color.attribute = ColorAttribute::NoController;
+ shared_memory->fullkey_color = {};
+ shared_memory->joycon_color.left = {};
+ shared_memory->joycon_color.right = {};
+ shared_memory->battery_level_dual = {};
+ shared_memory->battery_level_left = {};
+ shared_memory->battery_level_right = {};
+
switch (controller_type) {
case Core::HID::NpadStyleIndex::None:
- UNREACHABLE();
+ ASSERT(false);
break;
case Core::HID::NpadStyleIndex::ProController:
- shared_memory.style_tag.fullkey.Assign(1);
- shared_memory.device_type.fullkey.Assign(1);
- shared_memory.system_properties.is_vertical.Assign(1);
- shared_memory.system_properties.use_plus.Assign(1);
- shared_memory.system_properties.use_minus.Assign(1);
- shared_memory.applet_footer.type = AppletFooterUiType::SwitchProController;
+ shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
+ shared_memory->fullkey_color.fullkey = body_colors.fullkey;
+ shared_memory->battery_level_dual = battery_level.dual.battery_level;
+ shared_memory->style_tag.fullkey.Assign(1);
+ shared_memory->device_type.fullkey.Assign(1);
+ shared_memory->system_properties.is_vertical.Assign(1);
+ shared_memory->system_properties.use_plus.Assign(1);
+ shared_memory->system_properties.use_minus.Assign(1);
+ shared_memory->system_properties.is_charging_joy_dual.Assign(
+ battery_level.dual.is_charging);
+ shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController;
+ shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::Handheld:
- shared_memory.style_tag.handheld.Assign(1);
- shared_memory.device_type.handheld_left.Assign(1);
- shared_memory.device_type.handheld_right.Assign(1);
- shared_memory.system_properties.is_vertical.Assign(1);
- shared_memory.system_properties.use_plus.Assign(1);
- shared_memory.system_properties.use_minus.Assign(1);
- shared_memory.system_properties.use_directional_buttons.Assign(1);
- shared_memory.assignment_mode = NpadJoyAssignmentMode::Dual;
- shared_memory.applet_footer.type = AppletFooterUiType::HandheldJoyConLeftJoyConRight;
+ shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
+ shared_memory->joycon_color.attribute = ColorAttribute::Ok;
+ shared_memory->fullkey_color.fullkey = body_colors.fullkey;
+ shared_memory->joycon_color.left = body_colors.left;
+ shared_memory->joycon_color.right = body_colors.right;
+ shared_memory->style_tag.handheld.Assign(1);
+ shared_memory->device_type.handheld_left.Assign(1);
+ shared_memory->device_type.handheld_right.Assign(1);
+ shared_memory->system_properties.is_vertical.Assign(1);
+ shared_memory->system_properties.use_plus.Assign(1);
+ shared_memory->system_properties.use_minus.Assign(1);
+ shared_memory->system_properties.use_directional_buttons.Assign(1);
+ shared_memory->system_properties.is_charging_joy_dual.Assign(
+ battery_level.left.is_charging);
+ shared_memory->system_properties.is_charging_joy_left.Assign(
+ battery_level.left.is_charging);
+ shared_memory->system_properties.is_charging_joy_right.Assign(
+ battery_level.right.is_charging);
+ shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
+ shared_memory->applet_nfc_xcd.applet_footer.type =
+ AppletFooterUiType::HandheldJoyConLeftJoyConRight;
+ shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::JoyconDual:
- shared_memory.style_tag.joycon_dual.Assign(1);
+ shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
+ shared_memory->joycon_color.attribute = ColorAttribute::Ok;
+ shared_memory->style_tag.joycon_dual.Assign(1);
if (controller.is_dual_left_connected) {
- shared_memory.device_type.joycon_left.Assign(1);
- shared_memory.system_properties.use_minus.Assign(1);
+ shared_memory->joycon_color.left = body_colors.left;
+ shared_memory->battery_level_left = battery_level.left.battery_level;
+ shared_memory->device_type.joycon_left.Assign(1);
+ shared_memory->system_properties.use_minus.Assign(1);
+ shared_memory->system_properties.is_charging_joy_left.Assign(
+ battery_level.left.is_charging);
+ shared_memory->sixaxis_dual_left_properties.is_newly_assigned.Assign(1);
}
if (controller.is_dual_right_connected) {
- shared_memory.device_type.joycon_right.Assign(1);
- shared_memory.system_properties.use_plus.Assign(1);
+ shared_memory->joycon_color.right = body_colors.right;
+ shared_memory->battery_level_right = battery_level.right.battery_level;
+ shared_memory->device_type.joycon_right.Assign(1);
+ shared_memory->system_properties.use_plus.Assign(1);
+ shared_memory->system_properties.is_charging_joy_right.Assign(
+ battery_level.right.is_charging);
+ shared_memory->sixaxis_dual_right_properties.is_newly_assigned.Assign(1);
}
- shared_memory.system_properties.use_directional_buttons.Assign(1);
- shared_memory.system_properties.is_vertical.Assign(1);
- shared_memory.assignment_mode = NpadJoyAssignmentMode::Dual;
+ shared_memory->system_properties.use_directional_buttons.Assign(1);
+ shared_memory->system_properties.is_vertical.Assign(1);
+ shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
+
if (controller.is_dual_left_connected && controller.is_dual_right_connected) {
- shared_memory.applet_footer.type = AppletFooterUiType::JoyDual;
+ shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual;
+ shared_memory->fullkey_color.fullkey = body_colors.left;
+ shared_memory->battery_level_dual = battery_level.left.battery_level;
+ shared_memory->system_properties.is_charging_joy_dual.Assign(
+ battery_level.left.is_charging);
} else if (controller.is_dual_left_connected) {
- shared_memory.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly;
+ shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly;
+ shared_memory->fullkey_color.fullkey = body_colors.left;
+ shared_memory->battery_level_dual = battery_level.left.battery_level;
+ shared_memory->system_properties.is_charging_joy_dual.Assign(
+ battery_level.left.is_charging);
} else {
- shared_memory.applet_footer.type = AppletFooterUiType::JoyDualRightOnly;
+ shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly;
+ shared_memory->fullkey_color.fullkey = body_colors.right;
+ shared_memory->battery_level_dual = battery_level.right.battery_level;
+ shared_memory->system_properties.is_charging_joy_dual.Assign(
+ battery_level.right.is_charging);
}
break;
case Core::HID::NpadStyleIndex::JoyconLeft:
- shared_memory.style_tag.joycon_left.Assign(1);
- shared_memory.device_type.joycon_left.Assign(1);
- shared_memory.system_properties.is_horizontal.Assign(1);
- shared_memory.system_properties.use_minus.Assign(1);
- shared_memory.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal;
+ shared_memory->joycon_color.attribute = ColorAttribute::Ok;
+ shared_memory->joycon_color.left = body_colors.left;
+ shared_memory->battery_level_dual = battery_level.left.battery_level;
+ shared_memory->style_tag.joycon_left.Assign(1);
+ shared_memory->device_type.joycon_left.Assign(1);
+ shared_memory->system_properties.is_horizontal.Assign(1);
+ shared_memory->system_properties.use_minus.Assign(1);
+ shared_memory->system_properties.is_charging_joy_left.Assign(
+ battery_level.left.is_charging);
+ shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal;
+ shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::JoyconRight:
- shared_memory.style_tag.joycon_right.Assign(1);
- shared_memory.device_type.joycon_right.Assign(1);
- shared_memory.system_properties.is_horizontal.Assign(1);
- shared_memory.system_properties.use_plus.Assign(1);
- shared_memory.applet_footer.type = AppletFooterUiType::JoyRightHorizontal;
+ shared_memory->joycon_color.attribute = ColorAttribute::Ok;
+ shared_memory->joycon_color.right = body_colors.right;
+ shared_memory->battery_level_right = battery_level.right.battery_level;
+ shared_memory->style_tag.joycon_right.Assign(1);
+ shared_memory->device_type.joycon_right.Assign(1);
+ shared_memory->system_properties.is_horizontal.Assign(1);
+ shared_memory->system_properties.use_plus.Assign(1);
+ shared_memory->system_properties.is_charging_joy_right.Assign(
+ battery_level.right.is_charging);
+ shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal;
+ shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::GameCube:
- shared_memory.style_tag.gamecube.Assign(1);
- shared_memory.device_type.fullkey.Assign(1);
- shared_memory.system_properties.is_vertical.Assign(1);
- shared_memory.system_properties.use_plus.Assign(1);
+ shared_memory->style_tag.gamecube.Assign(1);
+ shared_memory->device_type.fullkey.Assign(1);
+ shared_memory->system_properties.is_vertical.Assign(1);
+ shared_memory->system_properties.use_plus.Assign(1);
break;
case Core::HID::NpadStyleIndex::Pokeball:
- shared_memory.style_tag.palma.Assign(1);
- shared_memory.device_type.palma.Assign(1);
+ shared_memory->style_tag.palma.Assign(1);
+ shared_memory->device_type.palma.Assign(1);
+ shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::NES:
- shared_memory.style_tag.lark.Assign(1);
- shared_memory.device_type.fullkey.Assign(1);
+ shared_memory->style_tag.lark.Assign(1);
+ shared_memory->device_type.fullkey.Assign(1);
break;
case Core::HID::NpadStyleIndex::SNES:
- shared_memory.style_tag.lucia.Assign(1);
- shared_memory.device_type.fullkey.Assign(1);
- shared_memory.applet_footer.type = AppletFooterUiType::Lucia;
+ shared_memory->style_tag.lucia.Assign(1);
+ shared_memory->device_type.fullkey.Assign(1);
+ shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lucia;
break;
case Core::HID::NpadStyleIndex::N64:
- shared_memory.style_tag.lagoon.Assign(1);
- shared_memory.device_type.fullkey.Assign(1);
- shared_memory.applet_footer.type = AppletFooterUiType::Lagon;
+ shared_memory->style_tag.lagoon.Assign(1);
+ shared_memory->device_type.fullkey.Assign(1);
+ shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lagon;
break;
case Core::HID::NpadStyleIndex::SegaGenesis:
- shared_memory.style_tag.lager.Assign(1);
- shared_memory.device_type.fullkey.Assign(1);
+ shared_memory->style_tag.lager.Assign(1);
+ shared_memory->device_type.fullkey.Assign(1);
break;
default:
break;
}
- const auto& body_colors = controller.device->GetColors();
-
- shared_memory.fullkey_color.attribute = ColorAttribute::Ok;
- shared_memory.fullkey_color.fullkey = body_colors.fullkey;
-
- shared_memory.joycon_color.attribute = ColorAttribute::Ok;
- shared_memory.joycon_color.left = body_colors.left;
- shared_memory.joycon_color.right = body_colors.right;
-
- // TODO: Investigate when we should report all batery types
- const auto& battery_level = controller.device->GetBattery();
- shared_memory.battery_level_dual = battery_level.dual.battery_level;
- shared_memory.battery_level_left = battery_level.left.battery_level;
- shared_memory.battery_level_right = battery_level.right.battery_level;
-
controller.is_connected = true;
controller.device->Connect();
SignalStyleSetChangedEvent(npad_id);
- WriteEmptyEntry(controller.shared_memory_entry);
+ WriteEmptyEntry(controller.shared_memory);
}
void Controller_NPad::OnInit() {
@@ -262,23 +348,18 @@ 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));
// Prefill controller buffers
for (auto& controller : controller_data) {
- auto& npad = controller.shared_memory_entry;
- npad.fullkey_color = {
+ auto* npad = controller.shared_memory;
+ npad->fullkey_color = {
.attribute = ColorAttribute::NoController,
.fullkey = {},
};
- npad.joycon_color = {
+ npad->joycon_color = {
.attribute = ColorAttribute::NoController,
.left = {},
.right = {},
@@ -288,38 +369,31 @@ 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) {
+void Controller_NPad::WriteEmptyEntry(NpadInternalState* npad) {
NPadGenericState dummy_pad_state{};
NpadGcTriggerState dummy_gc_state{};
- dummy_pad_state.sampling_number = npad.fullkey_lifo.ReadCurrentEntry().sampling_number + 1;
- npad.fullkey_lifo.WriteNextEntry(dummy_pad_state);
- dummy_pad_state.sampling_number = npad.handheld_lifo.ReadCurrentEntry().sampling_number + 1;
- npad.handheld_lifo.WriteNextEntry(dummy_pad_state);
- dummy_pad_state.sampling_number = npad.joy_dual_lifo.ReadCurrentEntry().sampling_number + 1;
- npad.joy_dual_lifo.WriteNextEntry(dummy_pad_state);
- dummy_pad_state.sampling_number = npad.joy_left_lifo.ReadCurrentEntry().sampling_number + 1;
- npad.joy_left_lifo.WriteNextEntry(dummy_pad_state);
- dummy_pad_state.sampling_number = npad.joy_right_lifo.ReadCurrentEntry().sampling_number + 1;
- npad.joy_right_lifo.WriteNextEntry(dummy_pad_state);
- dummy_pad_state.sampling_number = npad.palma_lifo.ReadCurrentEntry().sampling_number + 1;
- npad.palma_lifo.WriteNextEntry(dummy_pad_state);
- dummy_pad_state.sampling_number = npad.system_ext_lifo.ReadCurrentEntry().sampling_number + 1;
- npad.system_ext_lifo.WriteNextEntry(dummy_pad_state);
- dummy_gc_state.sampling_number = npad.gc_trigger_lifo.ReadCurrentEntry().sampling_number + 1;
- npad.gc_trigger_lifo.WriteNextEntry(dummy_gc_state);
+ dummy_pad_state.sampling_number = npad->fullkey_lifo.ReadCurrentEntry().sampling_number + 1;
+ npad->fullkey_lifo.WriteNextEntry(dummy_pad_state);
+ dummy_pad_state.sampling_number = npad->handheld_lifo.ReadCurrentEntry().sampling_number + 1;
+ npad->handheld_lifo.WriteNextEntry(dummy_pad_state);
+ dummy_pad_state.sampling_number = npad->joy_dual_lifo.ReadCurrentEntry().sampling_number + 1;
+ npad->joy_dual_lifo.WriteNextEntry(dummy_pad_state);
+ dummy_pad_state.sampling_number = npad->joy_left_lifo.ReadCurrentEntry().sampling_number + 1;
+ npad->joy_left_lifo.WriteNextEntry(dummy_pad_state);
+ dummy_pad_state.sampling_number = npad->joy_right_lifo.ReadCurrentEntry().sampling_number + 1;
+ npad->joy_right_lifo.WriteNextEntry(dummy_pad_state);
+ dummy_pad_state.sampling_number = npad->palma_lifo.ReadCurrentEntry().sampling_number + 1;
+ npad->palma_lifo.WriteNextEntry(dummy_pad_state);
+ dummy_pad_state.sampling_number = npad->system_ext_lifo.ReadCurrentEntry().sampling_number + 1;
+ npad->system_ext_lifo.WriteNextEntry(dummy_pad_state);
+ dummy_gc_state.sampling_number = npad->gc_trigger_lifo.ReadCurrentEntry().sampling_number + 1;
+ npad->gc_trigger_lifo.WriteNextEntry(dummy_gc_state);
}
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 +404,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()) {
@@ -381,23 +455,19 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
}
}
-void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
- std::size_t data_len) {
+void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
return;
}
for (std::size_t i = 0; i < controller_data.size(); ++i) {
auto& controller = controller_data[i];
- auto& npad = controller.shared_memory_entry;
+ auto* npad = controller.shared_memory;
const auto& controller_type = controller.device->GetNpadStyleIndex();
if (controller_type == Core::HID::NpadStyleIndex::None ||
!controller.device->IsConnected()) {
- // Refresh shared memory
- std::memcpy(data + NPAD_OFFSET + (i * sizeof(NpadInternalState)),
- &controller.shared_memory_entry, sizeof(NpadInternalState));
continue;
}
@@ -412,7 +482,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
libnx_state.connection_status.is_connected.Assign(1);
switch (controller_type) {
case Core::HID::NpadStyleIndex::None:
- UNREACHABLE();
+ ASSERT(false);
break;
case Core::HID::NpadStyleIndex::ProController:
case Core::HID::NpadStyleIndex::NES:
@@ -425,8 +495,8 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
libnx_state.connection_status.is_wired.Assign(1);
pad_state.sampling_number =
- npad.fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1;
- npad.fullkey_lifo.WriteNextEntry(pad_state);
+ npad->fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->fullkey_lifo.WriteNextEntry(pad_state);
break;
case Core::HID::NpadStyleIndex::Handheld:
pad_state.connection_status.raw = 0;
@@ -443,8 +513,8 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
libnx_state.connection_status.is_left_wired.Assign(1);
libnx_state.connection_status.is_right_wired.Assign(1);
pad_state.sampling_number =
- npad.handheld_lifo.ReadCurrentEntry().state.sampling_number + 1;
- npad.handheld_lifo.WriteNextEntry(pad_state);
+ npad->handheld_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->handheld_lifo.WriteNextEntry(pad_state);
break;
case Core::HID::NpadStyleIndex::JoyconDual:
pad_state.connection_status.raw = 0;
@@ -459,8 +529,8 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
}
pad_state.sampling_number =
- npad.joy_dual_lifo.ReadCurrentEntry().state.sampling_number + 1;
- npad.joy_dual_lifo.WriteNextEntry(pad_state);
+ npad->joy_dual_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->joy_dual_lifo.WriteNextEntry(pad_state);
break;
case Core::HID::NpadStyleIndex::JoyconLeft:
pad_state.connection_status.raw = 0;
@@ -469,8 +539,8 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
libnx_state.connection_status.is_left_connected.Assign(1);
pad_state.sampling_number =
- npad.joy_left_lifo.ReadCurrentEntry().state.sampling_number + 1;
- npad.joy_left_lifo.WriteNextEntry(pad_state);
+ npad->joy_left_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->joy_left_lifo.WriteNextEntry(pad_state);
break;
case Core::HID::NpadStyleIndex::JoyconRight:
pad_state.connection_status.raw = 0;
@@ -479,8 +549,8 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
libnx_state.connection_status.is_right_connected.Assign(1);
pad_state.sampling_number =
- npad.joy_right_lifo.ReadCurrentEntry().state.sampling_number + 1;
- npad.joy_right_lifo.WriteNextEntry(pad_state);
+ npad->joy_right_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->joy_right_lifo.WriteNextEntry(pad_state);
break;
case Core::HID::NpadStyleIndex::GameCube:
pad_state.connection_status.raw = 0;
@@ -489,18 +559,18 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
libnx_state.connection_status.is_wired.Assign(1);
pad_state.sampling_number =
- npad.fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1;
trigger_state.sampling_number =
- npad.gc_trigger_lifo.ReadCurrentEntry().state.sampling_number + 1;
- npad.fullkey_lifo.WriteNextEntry(pad_state);
- npad.gc_trigger_lifo.WriteNextEntry(trigger_state);
+ npad->gc_trigger_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->fullkey_lifo.WriteNextEntry(pad_state);
+ npad->gc_trigger_lifo.WriteNextEntry(trigger_state);
break;
case Core::HID::NpadStyleIndex::Pokeball:
pad_state.connection_status.raw = 0;
pad_state.connection_status.is_connected.Assign(1);
pad_state.sampling_number =
- npad.palma_lifo.ReadCurrentEntry().state.sampling_number + 1;
- npad.palma_lifo.WriteNextEntry(pad_state);
+ npad->palma_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->palma_lifo.WriteNextEntry(pad_state);
break;
default:
break;
@@ -509,17 +579,13 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
libnx_state.npad_buttons.raw = pad_state.npad_buttons.raw;
libnx_state.l_stick = pad_state.l_stick;
libnx_state.r_stick = pad_state.r_stick;
- npad.system_ext_lifo.WriteNextEntry(pad_state);
+ npad->system_ext_lifo.WriteNextEntry(pad_state);
press_state |= static_cast<u64>(pad_state.npad_buttons.raw);
-
- std::memcpy(data + NPAD_OFFSET + (i * sizeof(NpadInternalState)),
- &controller.shared_memory_entry, sizeof(NpadInternalState));
}
}
-void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
- std::size_t data_len) {
+void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
return;
}
@@ -534,7 +600,7 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
continue;
}
- auto& npad = controller.shared_memory_entry;
+ auto* npad = controller.shared_memory;
const auto& motion_state = controller.device->GetMotions();
auto& sixaxis_fullkey_state = controller.sixaxis_fullkey_state;
auto& sixaxis_handheld_state = controller.sixaxis_handheld_state;
@@ -543,6 +609,14 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
auto& sixaxis_left_lifo_state = controller.sixaxis_left_lifo_state;
auto& sixaxis_right_lifo_state = controller.sixaxis_right_lifo_state;
+ // Clear previous state
+ sixaxis_fullkey_state = {};
+ sixaxis_handheld_state = {};
+ sixaxis_dual_left_state = {};
+ sixaxis_dual_right_state = {};
+ sixaxis_left_lifo_state = {};
+ sixaxis_right_lifo_state = {};
+
if (controller.sixaxis_sensor_enabled && Settings::values.motion_enabled.GetValue()) {
controller.sixaxis_at_rest = true;
for (std::size_t e = 0; e < motion_state.size(); ++e) {
@@ -551,109 +625,116 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
}
}
+ const auto set_motion_state = [&](SixAxisSensorState& state,
+ const Core::HID::ControllerMotion& hid_state) {
+ using namespace std::literals::chrono_literals;
+ static constexpr SixAxisSensorState default_motion_state = {
+ .delta_time = std::chrono::nanoseconds(5ms).count(),
+ .accel = {0, 0, -1.0f},
+ .orientation =
+ {
+ Common::Vec3f{1.0f, 0, 0},
+ Common::Vec3f{0, 1.0f, 0},
+ Common::Vec3f{0, 0, 1.0f},
+ },
+ .attribute = {1},
+ };
+ if (!controller.sixaxis_sensor_enabled) {
+ state = default_motion_state;
+ return;
+ }
+ if (!Settings::values.motion_enabled.GetValue()) {
+ state = default_motion_state;
+ return;
+ }
+ state.attribute.is_connected.Assign(1);
+ state.delta_time = std::chrono::nanoseconds(5ms).count();
+ state.accel = hid_state.accel;
+ state.gyro = hid_state.gyro;
+ state.rotation = hid_state.rotation;
+ state.orientation = hid_state.orientation;
+ };
+
switch (controller_type) {
case Core::HID::NpadStyleIndex::None:
- UNREACHABLE();
+ ASSERT(false);
break;
case Core::HID::NpadStyleIndex::ProController:
- sixaxis_fullkey_state.attribute.raw = 0;
- if (controller.sixaxis_sensor_enabled) {
- sixaxis_fullkey_state.attribute.is_connected.Assign(1);
- sixaxis_fullkey_state.accel = motion_state[0].accel;
- sixaxis_fullkey_state.gyro = motion_state[0].gyro;
- sixaxis_fullkey_state.rotation = motion_state[0].rotation;
- sixaxis_fullkey_state.orientation = motion_state[0].orientation;
- }
+ set_motion_state(sixaxis_fullkey_state, motion_state[0]);
break;
case Core::HID::NpadStyleIndex::Handheld:
- sixaxis_handheld_state.attribute.raw = 0;
- if (controller.sixaxis_sensor_enabled) {
- sixaxis_handheld_state.attribute.is_connected.Assign(1);
- sixaxis_handheld_state.accel = motion_state[0].accel;
- sixaxis_handheld_state.gyro = motion_state[0].gyro;
- sixaxis_handheld_state.rotation = motion_state[0].rotation;
- sixaxis_handheld_state.orientation = motion_state[0].orientation;
- }
+ set_motion_state(sixaxis_handheld_state, motion_state[0]);
break;
case Core::HID::NpadStyleIndex::JoyconDual:
- sixaxis_dual_left_state.attribute.raw = 0;
- sixaxis_dual_right_state.attribute.raw = 0;
- if (controller.sixaxis_sensor_enabled) {
- // Set motion for the left joycon
- sixaxis_dual_left_state.attribute.is_connected.Assign(1);
- sixaxis_dual_left_state.accel = motion_state[0].accel;
- sixaxis_dual_left_state.gyro = motion_state[0].gyro;
- sixaxis_dual_left_state.rotation = motion_state[0].rotation;
- sixaxis_dual_left_state.orientation = motion_state[0].orientation;
- }
- if (controller.sixaxis_sensor_enabled) {
- // Set motion for the right joycon
- sixaxis_dual_right_state.attribute.is_connected.Assign(1);
- sixaxis_dual_right_state.accel = motion_state[1].accel;
- sixaxis_dual_right_state.gyro = motion_state[1].gyro;
- sixaxis_dual_right_state.rotation = motion_state[1].rotation;
- sixaxis_dual_right_state.orientation = motion_state[1].orientation;
- }
+ set_motion_state(sixaxis_dual_left_state, motion_state[0]);
+ set_motion_state(sixaxis_dual_right_state, motion_state[1]);
break;
case Core::HID::NpadStyleIndex::JoyconLeft:
- sixaxis_left_lifo_state.attribute.raw = 0;
- if (controller.sixaxis_sensor_enabled) {
- sixaxis_left_lifo_state.attribute.is_connected.Assign(1);
- sixaxis_left_lifo_state.accel = motion_state[0].accel;
- sixaxis_left_lifo_state.gyro = motion_state[0].gyro;
- sixaxis_left_lifo_state.rotation = motion_state[0].rotation;
- sixaxis_left_lifo_state.orientation = motion_state[0].orientation;
- }
+ set_motion_state(sixaxis_left_lifo_state, motion_state[0]);
break;
case Core::HID::NpadStyleIndex::JoyconRight:
- sixaxis_right_lifo_state.attribute.raw = 0;
- if (controller.sixaxis_sensor_enabled) {
- sixaxis_right_lifo_state.attribute.is_connected.Assign(1);
- sixaxis_right_lifo_state.accel = motion_state[1].accel;
- sixaxis_right_lifo_state.gyro = motion_state[1].gyro;
- sixaxis_right_lifo_state.rotation = motion_state[1].rotation;
- sixaxis_right_lifo_state.orientation = motion_state[1].orientation;
- }
+ set_motion_state(sixaxis_right_lifo_state, motion_state[1]);
+ break;
+ case Core::HID::NpadStyleIndex::Pokeball:
+ using namespace std::literals::chrono_literals;
+ set_motion_state(sixaxis_fullkey_state, motion_state[0]);
+ sixaxis_fullkey_state.delta_time = std::chrono::nanoseconds(15ms).count();
break;
default:
break;
}
sixaxis_fullkey_state.sampling_number =
- npad.sixaxis_fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->sixaxis_fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1;
sixaxis_handheld_state.sampling_number =
- npad.sixaxis_handheld_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->sixaxis_handheld_lifo.ReadCurrentEntry().state.sampling_number + 1;
sixaxis_dual_left_state.sampling_number =
- npad.sixaxis_dual_left_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->sixaxis_dual_left_lifo.ReadCurrentEntry().state.sampling_number + 1;
sixaxis_dual_right_state.sampling_number =
- npad.sixaxis_dual_right_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->sixaxis_dual_right_lifo.ReadCurrentEntry().state.sampling_number + 1;
sixaxis_left_lifo_state.sampling_number =
- npad.sixaxis_left_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->sixaxis_left_lifo.ReadCurrentEntry().state.sampling_number + 1;
sixaxis_right_lifo_state.sampling_number =
- npad.sixaxis_right_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ npad->sixaxis_right_lifo.ReadCurrentEntry().state.sampling_number + 1;
if (Core::HID::IndexToNpadIdType(i) == Core::HID::NpadIdType::Handheld) {
// This buffer only is updated on handheld on HW
- npad.sixaxis_handheld_lifo.WriteNextEntry(sixaxis_handheld_state);
+ npad->sixaxis_handheld_lifo.WriteNextEntry(sixaxis_handheld_state);
} else {
// Handheld doesn't update this buffer on HW
- npad.sixaxis_fullkey_lifo.WriteNextEntry(sixaxis_fullkey_state);
+ npad->sixaxis_fullkey_lifo.WriteNextEntry(sixaxis_fullkey_state);
}
- npad.sixaxis_dual_left_lifo.WriteNextEntry(sixaxis_dual_left_state);
- npad.sixaxis_dual_right_lifo.WriteNextEntry(sixaxis_dual_right_state);
- npad.sixaxis_left_lifo.WriteNextEntry(sixaxis_left_lifo_state);
- npad.sixaxis_right_lifo.WriteNextEntry(sixaxis_right_lifo_state);
- std::memcpy(data + NPAD_OFFSET + (i * sizeof(NpadInternalState)),
- &controller.shared_memory_entry, sizeof(NpadInternalState));
+ npad->sixaxis_dual_left_lifo.WriteNextEntry(sixaxis_dual_left_state);
+ npad->sixaxis_dual_right_lifo.WriteNextEntry(sixaxis_dual_right_state);
+ npad->sixaxis_left_lifo.WriteNextEntry(sixaxis_left_lifo_state);
+ npad->sixaxis_right_lifo.WriteNextEntry(sixaxis_right_lifo_state);
}
}
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();
}
@@ -674,6 +755,12 @@ std::size_t Controller_NPad::GetSupportedNpadIdTypesSize() const {
}
void Controller_NPad::SetHoldType(NpadJoyHoldType joy_hold_type) {
+ if (joy_hold_type != NpadJoyHoldType::Horizontal &&
+ joy_hold_type != NpadJoyHoldType::Vertical) {
+ LOG_ERROR(Service_HID, "Npad joy hold type needs to be valid, joy_hold_type={}",
+ joy_hold_type);
+ return;
+ }
hold_type = joy_hold_type;
}
@@ -682,6 +769,11 @@ Controller_NPad::NpadJoyHoldType Controller_NPad::GetHoldType() const {
}
void Controller_NPad::SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode) {
+ if (activation_mode >= NpadHandheldActivationMode::MaxActivationMode) {
+ ASSERT_MSG(false, "Activation mode should be always None, Single or Dual");
+ return;
+ }
+
handheld_activation_mode = activation_mode;
}
@@ -697,20 +789,21 @@ Controller_NPad::NpadCommunicationMode Controller_NPad::GetNpadCommunicationMode
return communication_mode;
}
-void Controller_NPad::SetNpadMode(Core::HID::NpadIdType npad_id, NpadJoyDeviceType npad_device_type,
- NpadJoyAssignmentMode assignment_mode) {
+Result Controller_NPad::SetNpadMode(Core::HID::NpadIdType npad_id,
+ NpadJoyDeviceType npad_device_type,
+ NpadJoyAssignmentMode assignment_mode) {
if (!IsNpadIdValid(npad_id)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
- return;
+ return InvalidNpadId;
}
auto& controller = GetControllerFromNpadIdType(npad_id);
- if (controller.shared_memory_entry.assignment_mode != assignment_mode) {
- controller.shared_memory_entry.assignment_mode = assignment_mode;
+ if (controller.shared_memory->assignment_mode != assignment_mode) {
+ controller.shared_memory->assignment_mode = assignment_mode;
}
if (!controller.device->IsConnected()) {
- return;
+ return ResultSuccess;
}
if (assignment_mode == NpadJoyAssignmentMode::Dual) {
@@ -719,34 +812,34 @@ void Controller_NPad::SetNpadMode(Core::HID::NpadIdType npad_id, NpadJoyDeviceTy
controller.is_dual_left_connected = true;
controller.is_dual_right_connected = false;
UpdateControllerAt(Core::HID::NpadStyleIndex::JoyconDual, npad_id, true);
- return;
+ return ResultSuccess;
}
if (controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconRight) {
DisconnectNpad(npad_id);
controller.is_dual_left_connected = false;
controller.is_dual_right_connected = true;
UpdateControllerAt(Core::HID::NpadStyleIndex::JoyconDual, npad_id, true);
- return;
+ return ResultSuccess;
}
- return;
+ return ResultSuccess;
}
// This is for NpadJoyAssignmentMode::Single
// Only JoyconDual get affected by this function
if (controller.device->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::JoyconDual) {
- return;
+ return ResultSuccess;
}
if (controller.is_dual_left_connected && !controller.is_dual_right_connected) {
DisconnectNpad(npad_id);
UpdateControllerAt(Core::HID::NpadStyleIndex::JoyconLeft, npad_id, true);
- return;
+ return ResultSuccess;
}
if (!controller.is_dual_left_connected && controller.is_dual_right_connected) {
DisconnectNpad(npad_id);
UpdateControllerAt(Core::HID::NpadStyleIndex::JoyconRight, npad_id, true);
- return;
+ return ResultSuccess;
}
// We have two controllers connected to the same npad_id we need to split them
@@ -764,6 +857,7 @@ void Controller_NPad::SetNpadMode(Core::HID::NpadIdType npad_id, NpadJoyDeviceTy
controller_2.is_dual_right_connected = false;
UpdateControllerAt(Core::HID::NpadStyleIndex::JoyconDual, npad_id_2, true);
}
+ return ResultSuccess;
}
bool Controller_NPad::VibrateControllerAtIndex(Core::HID::NpadIdType npad_id,
@@ -795,11 +889,11 @@ bool Controller_NPad::VibrateControllerAtIndex(Core::HID::NpadIdType npad_id,
const auto now = steady_clock::now();
- // Filter out non-zero vibrations that are within 10ms of each other.
+ // Filter out non-zero vibrations that are within 15ms of each other.
if ((vibration_value.low_amplitude != 0.0f || vibration_value.high_amplitude != 0.0f) &&
duration_cast<milliseconds>(
now - controller.vibration[device_index].last_vibration_timepoint) <
- milliseconds(10)) {
+ milliseconds(15)) {
return false;
}
@@ -815,7 +909,7 @@ bool Controller_NPad::VibrateControllerAtIndex(Core::HID::NpadIdType npad_id,
void Controller_NPad::VibrateController(
const Core::HID::VibrationDeviceHandle& vibration_device_handle,
const Core::HID::VibrationValue& vibration_value) {
- if (!IsDeviceHandleValid(vibration_device_handle)) {
+ if (IsDeviceHandleValid(vibration_device_handle).IsError()) {
return;
}
@@ -831,7 +925,7 @@ void Controller_NPad::VibrateController(
}
if (vibration_device_handle.device_index == Core::HID::DeviceIndex::None) {
- UNREACHABLE_MSG("DeviceIndex should never be None!");
+ ASSERT_MSG(false, "DeviceIndex should never be None!");
return;
}
@@ -878,7 +972,7 @@ void Controller_NPad::VibrateControllers(
Core::HID::VibrationValue Controller_NPad::GetLastVibration(
const Core::HID::VibrationDeviceHandle& vibration_device_handle) const {
- if (!IsDeviceHandleValid(vibration_device_handle)) {
+ if (IsDeviceHandleValid(vibration_device_handle).IsError()) {
return {};
}
@@ -889,7 +983,7 @@ Core::HID::VibrationValue Controller_NPad::GetLastVibration(
void Controller_NPad::InitializeVibrationDevice(
const Core::HID::VibrationDeviceHandle& vibration_device_handle) {
- if (!IsDeviceHandleValid(vibration_device_handle)) {
+ if (IsDeviceHandleValid(vibration_device_handle).IsError()) {
return;
}
@@ -916,7 +1010,7 @@ void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) {
bool Controller_NPad::IsVibrationDeviceMounted(
const Core::HID::VibrationDeviceHandle& vibration_device_handle) const {
- if (!IsDeviceHandleValid(vibration_device_handle)) {
+ if (IsDeviceHandleValid(vibration_device_handle).IsError()) {
return false;
}
@@ -959,10 +1053,10 @@ void Controller_NPad::UpdateControllerAt(Core::HID::NpadStyleIndex type,
InitNewlyAddedController(npad_id);
}
-void Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
+Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
if (!IsNpadIdValid(npad_id)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
- return;
+ return InvalidNpadId;
}
LOG_DEBUG(Service_HID, "Npad disconnected {}", npad_id);
@@ -973,188 +1067,300 @@ void Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
controller.vibration[device_idx].device_mounted = false;
}
- auto& shared_memory_entry = controller.shared_memory_entry;
- // Don't reset shared_memory_entry.assignment_mode this value is persistent
- shared_memory_entry.style_tag.raw = Core::HID::NpadStyleSet::None; // Zero out
- shared_memory_entry.device_type.raw = 0;
- shared_memory_entry.system_properties.raw = 0;
- shared_memory_entry.button_properties.raw = 0;
- shared_memory_entry.battery_level_dual = 0;
- shared_memory_entry.battery_level_left = 0;
- shared_memory_entry.battery_level_right = 0;
- shared_memory_entry.fullkey_color = {
+ auto* shared_memory = controller.shared_memory;
+ // Don't reset shared_memory->assignment_mode this value is persistent
+ shared_memory->style_tag.raw = Core::HID::NpadStyleSet::None; // Zero out
+ shared_memory->device_type.raw = 0;
+ shared_memory->system_properties.raw = 0;
+ shared_memory->button_properties.raw = 0;
+ shared_memory->sixaxis_fullkey_properties.raw = 0;
+ shared_memory->sixaxis_handheld_properties.raw = 0;
+ shared_memory->sixaxis_dual_left_properties.raw = 0;
+ shared_memory->sixaxis_dual_right_properties.raw = 0;
+ shared_memory->sixaxis_left_properties.raw = 0;
+ shared_memory->sixaxis_right_properties.raw = 0;
+ shared_memory->battery_level_dual = 0;
+ shared_memory->battery_level_left = 0;
+ shared_memory->battery_level_right = 0;
+ shared_memory->fullkey_color = {
.attribute = ColorAttribute::NoController,
.fullkey = {},
};
- shared_memory_entry.joycon_color = {
+ shared_memory->joycon_color = {
.attribute = ColorAttribute::NoController,
.left = {},
.right = {},
};
- shared_memory_entry.applet_footer.type = AppletFooterUiType::None;
+ shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::None;
controller.is_dual_left_connected = true;
controller.is_dual_right_connected = true;
controller.is_connected = false;
controller.device->Disconnect();
SignalStyleSetChangedEvent(npad_id);
- WriteEmptyEntry(controller.shared_memory_entry);
+ WriteEmptyEntry(shared_memory);
+ return ResultSuccess;
}
+Result Controller_NPad::SetGyroscopeZeroDriftMode(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle, GyroscopeZeroDriftMode drift_mode) {
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
-void Controller_NPad::SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle,
- GyroscopeZeroDriftMode drift_mode) {
- if (!IsDeviceHandleValid(sixaxis_handle)) {
- LOG_ERROR(Service_HID, "Invalid handle");
- return;
+ auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ sixaxis.gyroscope_zero_drift_mode = drift_mode;
+
+ return ResultSuccess;
+}
+
+Result Controller_NPad::GetGyroscopeZeroDriftMode(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ GyroscopeZeroDriftMode& drift_mode) const {
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
}
- auto& controller = GetControllerFromHandle(sixaxis_handle);
- controller.gyroscope_zero_drift_mode = drift_mode;
+
+ const auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ drift_mode = sixaxis.gyroscope_zero_drift_mode;
+
+ return ResultSuccess;
}
-Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMode(
- Core::HID::SixAxisSensorHandle sixaxis_handle) const {
- if (!IsDeviceHandleValid(sixaxis_handle)) {
- LOG_ERROR(Service_HID, "Invalid handle");
- // Return the default value
- return GyroscopeZeroDriftMode::Standard;
+Result Controller_NPad::IsSixAxisSensorAtRest(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool& is_at_rest) const {
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
}
+
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 {
- if (!IsDeviceHandleValid(sixaxis_handle)) {
- LOG_ERROR(Service_HID, "Invalid handle");
- // Return the default value
- return true;
+Result Controller_NPad::IsFirmwareUpdateAvailableForSixAxisSensor(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_firmware_available) const {
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
}
- const auto& controller = GetControllerFromHandle(sixaxis_handle);
- return controller.sixaxis_at_rest;
+
+ const auto& sixaxis_properties = GetSixaxisProperties(sixaxis_handle);
+ is_firmware_available = sixaxis_properties.is_firmware_update_available != 0;
+ return ResultSuccess;
}
-void Controller_NPad::SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle,
- bool sixaxis_status) {
- if (!IsDeviceHandleValid(sixaxis_handle)) {
- LOG_ERROR(Service_HID, "Invalid handle");
- return;
+Result Controller_NPad::EnableSixAxisSensorUnalteredPassthrough(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool is_enabled) {
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ sixaxis.unaltered_passtrough = is_enabled;
+ return ResultSuccess;
+}
+
+Result Controller_NPad::IsSixAxisSensorUnalteredPassthroughEnabled(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_enabled) const {
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ const auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ is_enabled = sixaxis.unaltered_passtrough;
+ return ResultSuccess;
+}
+
+Result Controller_NPad::LoadSixAxisSensorCalibrationParameter(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorCalibrationParameter& calibration) const {
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ // TODO: Request this data to the controller. On error return 0xd8ca
+ const auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ calibration = sixaxis.calibration;
+ return ResultSuccess;
+}
+
+Result Controller_NPad::GetSixAxisSensorIcInformation(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorIcInformation& ic_information) const {
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ // TODO: Request this data to the controller. On error return 0xd8ca
+ const auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ ic_information = sixaxis.ic_information;
+ return ResultSuccess;
+}
+
+Result Controller_NPad::ResetIsSixAxisSensorDeviceNewlyAssigned(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle) {
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
}
+
+ auto& sixaxis_properties = GetSixaxisProperties(sixaxis_handle);
+ sixaxis_properties.is_newly_assigned.Assign(0);
+
+ return ResultSuccess;
+}
+
+Result Controller_NPad::SetSixAxisEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool sixaxis_status) {
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
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) {
- if (!IsDeviceHandleValid(sixaxis_handle)) {
- LOG_ERROR(Service_HID, "Invalid handle");
- return;
+Result Controller_NPad::IsSixAxisSensorFusionEnabled(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_fusion_enabled) const {
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
}
- auto& controller = GetControllerFromHandle(sixaxis_handle);
- controller.sixaxis_fusion_enabled = sixaxis_fusion_status;
+
+ const auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ is_fusion_enabled = sixaxis.is_fusion_enabled;
+
+ return ResultSuccess;
+}
+Result Controller_NPad::SetSixAxisFusionEnabled(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool is_fusion_enabled) {
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ sixaxis.is_fusion_enabled = is_fusion_enabled;
+
+ return ResultSuccess;
}
-void Controller_NPad::SetSixAxisFusionParameters(
- Core::HID::SixAxisSensorHandle sixaxis_handle,
+Result Controller_NPad::SetSixAxisFusionParameters(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters) {
- if (!IsDeviceHandleValid(sixaxis_handle)) {
- LOG_ERROR(Service_HID, "Invalid handle");
- return;
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
}
- auto& controller = GetControllerFromHandle(sixaxis_handle);
- controller.sixaxis_fusion = sixaxis_fusion_parameters;
-}
-Core::HID::SixAxisSensorFusionParameters Controller_NPad::GetSixAxisFusionParameters(
- Core::HID::SixAxisSensorHandle sixaxis_handle) {
- if (!IsDeviceHandleValid(sixaxis_handle)) {
- LOG_ERROR(Service_HID, "Invalid handle");
- // Since these parameters are unknow just return zeros
- return {};
+ 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;
+
+ auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ sixaxis.fusion = sixaxis_fusion_parameters;
+
+ return ResultSuccess;
}
-void Controller_NPad::ResetSixAxisFusionParameters(Core::HID::SixAxisSensorHandle sixaxis_handle) {
- if (!IsDeviceHandleValid(sixaxis_handle)) {
- LOG_ERROR(Service_HID, "Invalid handle");
- return;
+Result Controller_NPad::GetSixAxisFusionParameters(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorFusionParameters& parameters) const {
+ const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
}
- auto& controller = GetControllerFromHandle(sixaxis_handle);
- // Since these parameters are unknow just fill with zeros
- controller.sixaxis_fusion = {};
+
+ const auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ parameters = sixaxis.fusion;
+
+ return ResultSuccess;
}
-void Controller_NPad::MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1,
- Core::HID::NpadIdType npad_id_2) {
+Result Controller_NPad::MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1,
+ Core::HID::NpadIdType npad_id_2) {
if (!IsNpadIdValid(npad_id_1) || !IsNpadIdValid(npad_id_2)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id_1:{}, npad_id_2:{}", npad_id_1,
npad_id_2);
- return;
+ return InvalidNpadId;
}
auto& controller_1 = GetControllerFromNpadIdType(npad_id_1);
auto& controller_2 = GetControllerFromNpadIdType(npad_id_2);
- const auto controller_style_1 = controller_1.device->GetNpadStyleIndex();
- const auto controller_style_2 = controller_2.device->GetNpadStyleIndex();
- bool merge_controllers = false;
+ auto controller_style_1 = controller_1.device->GetNpadStyleIndex();
+ auto controller_style_2 = controller_2.device->GetNpadStyleIndex();
+
+ // Simplify this code by converting dualjoycon with only a side connected to single joycons
+ if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual) {
+ if (controller_1.is_dual_left_connected && !controller_1.is_dual_right_connected) {
+ controller_style_1 = Core::HID::NpadStyleIndex::JoyconLeft;
+ }
+ if (!controller_1.is_dual_left_connected && controller_1.is_dual_right_connected) {
+ controller_style_1 = Core::HID::NpadStyleIndex::JoyconRight;
+ }
+ }
+ if (controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual) {
+ if (controller_2.is_dual_left_connected && !controller_2.is_dual_right_connected) {
+ controller_style_2 = Core::HID::NpadStyleIndex::JoyconLeft;
+ }
+ if (!controller_2.is_dual_left_connected && controller_2.is_dual_right_connected) {
+ controller_style_2 = Core::HID::NpadStyleIndex::JoyconRight;
+ }
+ }
- // If the controllers at both npad indices form a pair of left and right joycons, merge them.
- // Otherwise, do nothing.
+ // Invalid merge errors
+ if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual ||
+ controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual) {
+ return NpadIsDualJoycon;
+ }
if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconLeft &&
+ controller_style_2 == Core::HID::NpadStyleIndex::JoyconLeft) {
+ return NpadIsSameType;
+ }
+ if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconRight &&
controller_style_2 == Core::HID::NpadStyleIndex::JoyconRight) {
- merge_controllers = true;
- }
- if (controller_style_2 == Core::HID::NpadStyleIndex::JoyconLeft &&
- controller_style_1 == Core::HID::NpadStyleIndex::JoyconRight) {
- merge_controllers = true;
- }
- if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual &&
- controller_style_2 == Core::HID::NpadStyleIndex::JoyconRight &&
- controller_1.is_dual_left_connected && !controller_1.is_dual_right_connected) {
- merge_controllers = true;
- }
- if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual &&
- controller_style_2 == Core::HID::NpadStyleIndex::JoyconLeft &&
- !controller_1.is_dual_left_connected && controller_1.is_dual_right_connected) {
- merge_controllers = true;
- }
- if (controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual &&
- controller_style_1 == Core::HID::NpadStyleIndex::JoyconRight &&
- controller_2.is_dual_left_connected && !controller_2.is_dual_right_connected) {
- merge_controllers = true;
- }
- if (controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual &&
- controller_style_1 == Core::HID::NpadStyleIndex::JoyconLeft &&
- !controller_2.is_dual_left_connected && controller_2.is_dual_right_connected) {
- merge_controllers = true;
- }
- if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual &&
- controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual &&
- controller_1.is_dual_left_connected && !controller_1.is_dual_right_connected &&
- !controller_2.is_dual_left_connected && controller_2.is_dual_right_connected) {
- merge_controllers = true;
- }
- if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual &&
- controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual &&
- !controller_1.is_dual_left_connected && controller_1.is_dual_right_connected &&
- controller_2.is_dual_left_connected && !controller_2.is_dual_right_connected) {
- merge_controllers = true;
- }
-
- if (merge_controllers) {
- // Disconnect the joycon at the second id and connect the dual joycon at the first index.
- DisconnectNpad(npad_id_2);
- controller_1.is_dual_left_connected = true;
- controller_1.is_dual_right_connected = true;
- AddNewControllerAt(Core::HID::NpadStyleIndex::JoyconDual, npad_id_1);
- return;
+ return NpadIsSameType;
+ }
+
+ // These exceptions are handled as if they where dual joycon
+ if (controller_style_1 != Core::HID::NpadStyleIndex::JoyconLeft &&
+ controller_style_1 != Core::HID::NpadStyleIndex::JoyconRight) {
+ return NpadIsDualJoycon;
+ }
+ if (controller_style_2 != Core::HID::NpadStyleIndex::JoyconLeft &&
+ controller_style_2 != Core::HID::NpadStyleIndex::JoyconRight) {
+ return NpadIsDualJoycon;
}
- LOG_WARNING(Service_HID,
- "Controllers can't be merged npad_id_1:{}, npad_id_2:{}, type_1:{}, type_2:{}, "
- "dual_1(left/right):{}/{}, dual_2(left/right):{}/{}",
- npad_id_1, npad_id_2, controller_1.device->GetNpadStyleIndex(),
- controller_2.device->GetNpadStyleIndex(), controller_1.is_dual_left_connected,
- controller_1.is_dual_right_connected, controller_2.is_dual_left_connected,
- controller_2.is_dual_right_connected);
+
+ // Disconnect the joycon at the second id and connect the dual joycon at the first index.
+ DisconnectNpad(npad_id_2);
+ controller_1.is_dual_left_connected = true;
+ controller_1.is_dual_right_connected = true;
+ AddNewControllerAt(Core::HID::NpadStyleIndex::JoyconDual, npad_id_1);
+ return ResultSuccess;
}
void Controller_NPad::StartLRAssignmentMode() {
@@ -1167,17 +1373,17 @@ void Controller_NPad::StopLRAssignmentMode() {
is_in_lr_assignment_mode = false;
}
-bool Controller_NPad::SwapNpadAssignment(Core::HID::NpadIdType npad_id_1,
- Core::HID::NpadIdType npad_id_2) {
+Result Controller_NPad::SwapNpadAssignment(Core::HID::NpadIdType npad_id_1,
+ Core::HID::NpadIdType npad_id_2) {
if (!IsNpadIdValid(npad_id_1) || !IsNpadIdValid(npad_id_2)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id_1:{}, npad_id_2:{}", npad_id_1,
npad_id_2);
- return false;
+ return InvalidNpadId;
}
if (npad_id_1 == Core::HID::NpadIdType::Handheld ||
npad_id_2 == Core::HID::NpadIdType::Handheld || npad_id_1 == Core::HID::NpadIdType::Other ||
npad_id_2 == Core::HID::NpadIdType::Other) {
- return true;
+ return ResultSuccess;
}
const auto& controller_1 = GetControllerFromNpadIdType(npad_id_1).device;
const auto& controller_2 = GetControllerFromNpadIdType(npad_id_2).device;
@@ -1187,46 +1393,49 @@ bool Controller_NPad::SwapNpadAssignment(Core::HID::NpadIdType npad_id_1,
const auto is_connected_2 = controller_2->IsConnected();
if (!IsControllerSupported(type_index_1) && is_connected_1) {
- return false;
+ return NpadNotConnected;
}
if (!IsControllerSupported(type_index_2) && is_connected_2) {
- return false;
+ return NpadNotConnected;
}
UpdateControllerAt(type_index_2, npad_id_1, is_connected_2);
UpdateControllerAt(type_index_1, npad_id_2, is_connected_1);
- return true;
+ return ResultSuccess;
}
-Core::HID::LedPattern Controller_NPad::GetLedPattern(Core::HID::NpadIdType npad_id) {
+Result Controller_NPad::GetLedPattern(Core::HID::NpadIdType npad_id,
+ Core::HID::LedPattern& pattern) const {
if (!IsNpadIdValid(npad_id)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
- return Core::HID::LedPattern{0, 0, 0, 0};
+ return InvalidNpadId;
}
const auto& controller = GetControllerFromNpadIdType(npad_id).device;
- return controller->GetLedPattern();
+ pattern = controller->GetLedPattern();
+ return ResultSuccess;
}
-bool Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled(
- Core::HID::NpadIdType npad_id) const {
+Result Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::NpadIdType npad_id,
+ bool& is_valid) const {
if (!IsNpadIdValid(npad_id)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
- // Return the default value
- return false;
+ return InvalidNpadId;
}
const auto& controller = GetControllerFromNpadIdType(npad_id);
- return controller.unintended_home_button_input_protection;
+ is_valid = controller.unintended_home_button_input_protection;
+ return ResultSuccess;
}
-void Controller_NPad::SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled,
- Core::HID::NpadIdType npad_id) {
+Result Controller_NPad::SetUnintendedHomeButtonInputProtectionEnabled(
+ bool is_protection_enabled, Core::HID::NpadIdType npad_id) {
if (!IsNpadIdValid(npad_id)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
- return;
+ return InvalidNpadId;
}
auto& controller = GetControllerFromNpadIdType(npad_id);
controller.unintended_home_button_input_protection = is_protection_enabled;
+ return ResultSuccess;
}
void Controller_NPad::SetAnalogStickUseCenterClamp(bool use_center_clamp) {
@@ -1364,4 +1573,96 @@ const Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromNpa
return controller_data[npad_index];
}
+Core::HID::SixAxisSensorProperties& Controller_NPad::GetSixaxisProperties(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle) {
+ auto& controller = GetControllerFromHandle(sixaxis_handle);
+ switch (sixaxis_handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Pokeball:
+ return controller.shared_memory->sixaxis_fullkey_properties;
+ case Core::HID::NpadStyleIndex::Handheld:
+ return controller.shared_memory->sixaxis_handheld_properties;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
+ return controller.shared_memory->sixaxis_dual_left_properties;
+ }
+ return controller.shared_memory->sixaxis_dual_right_properties;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ return controller.shared_memory->sixaxis_left_properties;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ return controller.shared_memory->sixaxis_right_properties;
+ default:
+ return controller.shared_memory->sixaxis_fullkey_properties;
+ }
+}
+
+const Core::HID::SixAxisSensorProperties& Controller_NPad::GetSixaxisProperties(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle) const {
+ const auto& controller = GetControllerFromHandle(sixaxis_handle);
+ switch (sixaxis_handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Pokeball:
+ return controller.shared_memory->sixaxis_fullkey_properties;
+ case Core::HID::NpadStyleIndex::Handheld:
+ return controller.shared_memory->sixaxis_handheld_properties;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
+ return controller.shared_memory->sixaxis_dual_left_properties;
+ }
+ return controller.shared_memory->sixaxis_dual_right_properties;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ return controller.shared_memory->sixaxis_left_properties;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ return controller.shared_memory->sixaxis_right_properties;
+ default:
+ return controller.shared_memory->sixaxis_fullkey_properties;
+ }
+}
+
+Controller_NPad::SixaxisParameters& Controller_NPad::GetSixaxisState(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle) {
+ auto& controller = GetControllerFromHandle(sixaxis_handle);
+ switch (sixaxis_handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Pokeball:
+ return controller.sixaxis_fullkey;
+ case Core::HID::NpadStyleIndex::Handheld:
+ return controller.sixaxis_handheld;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
+ return controller.sixaxis_dual_left;
+ }
+ return controller.sixaxis_dual_right;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ return controller.sixaxis_left;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ return controller.sixaxis_right;
+ default:
+ return controller.sixaxis_unknown;
+ }
+}
+
+const Controller_NPad::SixaxisParameters& Controller_NPad::GetSixaxisState(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle) const {
+ const auto& controller = GetControllerFromHandle(sixaxis_handle);
+ switch (sixaxis_handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Pokeball:
+ return controller.sixaxis_fullkey;
+ case Core::HID::NpadStyleIndex::Handheld:
+ return controller.sixaxis_handheld;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
+ return controller.sixaxis_dual_left;
+ }
+ return controller.sixaxis_dual_right;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ return controller.sixaxis_left;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ return controller.sixaxis_right;
+ default:
+ return controller.sixaxis_unknown;
+ }
+}
+
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 6b2872bad..1a589cca2 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,7 +9,8 @@
#include "common/bit_field.h"
#include "common/common_types.h"
-#include "common/quaternion.h"
+#include "common/vector_math.h"
+
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
@@ -27,13 +27,15 @@ class KReadableEvent;
namespace Service::KernelHelpers {
class ServiceContext;
-}
+} // namespace Service::KernelHelpers
+
+union Result;
namespace Service::HID {
class Controller_NPad final : public ControllerBase {
public:
- explicit Controller_NPad(Core::HID::HIDCore& hid_core_,
+ explicit Controller_NPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
KernelHelpers::ServiceContext& service_context_);
~Controller_NPad() override;
@@ -44,11 +46,10 @@ public:
void OnRelease() override;
// When the controller is requesting an update for the shared memory
- void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
// When the controller is requesting a motion update for the shared memory
- void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
- std::size_t size) override;
+ void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing) override;
// This is nn::hid::GyroscopeZeroDriftMode
enum class GyroscopeZeroDriftMode : u32 {
@@ -80,6 +81,7 @@ public:
Dual = 0,
Single = 1,
None = 2,
+ MaxActivationMode = 3,
};
// This is nn::hid::NpadCommunicationMode
@@ -106,8 +108,8 @@ public:
void SetNpadCommunicationMode(NpadCommunicationMode communication_mode_);
NpadCommunicationMode GetNpadCommunicationMode() const;
- void SetNpadMode(Core::HID::NpadIdType npad_id, NpadJoyDeviceType npad_device_type,
- NpadJoyAssignmentMode assignment_mode);
+ Result SetNpadMode(Core::HID::NpadIdType npad_id, NpadJoyDeviceType npad_device_type,
+ NpadJoyAssignmentMode assignment_mode);
bool VibrateControllerAtIndex(Core::HID::NpadIdType npad_id, std::size_t device_index,
const Core::HID::VibrationValue& vibration_value);
@@ -140,46 +142,68 @@ public:
void UpdateControllerAt(Core::HID::NpadStyleIndex controller, Core::HID::NpadIdType npad_id,
bool connected);
- 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(
- Core::HID::SixAxisSensorHandle sixaxis_handle,
+ Result DisconnectNpad(Core::HID::NpadIdType npad_id);
+
+ Result SetGyroscopeZeroDriftMode(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ GyroscopeZeroDriftMode drift_mode);
+ Result GetGyroscopeZeroDriftMode(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ GyroscopeZeroDriftMode& drift_mode) const;
+ Result IsSixAxisSensorAtRest(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool& is_at_rest) const;
+ Result IsFirmwareUpdateAvailableForSixAxisSensor(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_firmware_available) const;
+ Result EnableSixAxisSensorUnalteredPassthrough(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool is_enabled);
+ Result IsSixAxisSensorUnalteredPassthroughEnabled(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_enabled) const;
+ Result LoadSixAxisSensorCalibrationParameter(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorCalibrationParameter& calibration) const;
+ Result GetSixAxisSensorIcInformation(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorIcInformation& ic_information) const;
+ Result ResetIsSixAxisSensorDeviceNewlyAssigned(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle);
+ Result SetSixAxisEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool sixaxis_status);
+ Result IsSixAxisSensorFusionEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool& is_fusion_enabled) const;
+ Result SetSixAxisFusionEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool is_fusion_enabled);
+ Result SetSixAxisFusionParameters(
+ const 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);
- Core::HID::LedPattern GetLedPattern(Core::HID::NpadIdType npad_id);
- bool IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::NpadIdType npad_id) const;
- void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled,
- Core::HID::NpadIdType npad_id);
+ Result GetSixAxisFusionParameters(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorFusionParameters& parameters) const;
+ Result GetLedPattern(Core::HID::NpadIdType npad_id, Core::HID::LedPattern& pattern) const;
+ Result IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::NpadIdType npad_id,
+ bool& is_enabled) const;
+ Result SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled,
+ Core::HID::NpadIdType npad_id);
void SetAnalogStickUseCenterClamp(bool use_center_clamp);
void ClearAllConnectedControllers();
void DisconnectAllConnectedControllers();
void ConnectAllDisconnectedControllers();
void ClearAllControllers();
- void MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2);
+ Result MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1,
+ Core::HID::NpadIdType npad_id_2);
void StartLRAssignmentMode();
void StopLRAssignmentMode();
- bool SwapNpadAssignment(Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2);
+ Result SwapNpadAssignment(Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2);
// Logical OR for all buttons presses on all controllers
// Specifically for cheat engine and other features.
Core::HID::NpadButton GetAndResetPressState();
static bool IsNpadIdValid(Core::HID::NpadIdType npad_id);
- static bool IsDeviceHandleValid(const Core::HID::SixAxisSensorHandle& device_handle);
- static bool IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle);
+ static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle);
+ static Result VerifyValidSixAxisSensorHandle(
+ const Core::HID::SixAxisSensorHandle& device_handle);
private:
+ static constexpr std::size_t NPAD_COUNT = 10;
+
// This is nn::hid::detail::ColorAttribute
enum class ColorAttribute : u32 {
Ok = 0,
@@ -190,16 +214,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");
@@ -225,11 +249,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");
@@ -252,7 +276,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");
@@ -324,11 +348,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");
@@ -365,8 +389,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");
@@ -401,46 +425,54 @@ private:
U,
};
+ struct AppletNfcXcd {
+ union {
+ AppletFooterUi applet_footer{};
+ Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo;
+ };
+ };
+
// 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;
- union {
- Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo{};
- AppletFooterUi applet_footer;
- };
+ NPadSystemProperties system_properties{};
+ NpadSystemButtonProperties button_properties{};
+ Core::HID::NpadBatteryLevel battery_level_dual{};
+ Core::HID::NpadBatteryLevel battery_level_left{};
+ Core::HID::NpadBatteryLevel battery_level_right{};
+ AppletNfcXcd applet_nfc_xcd{};
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;
- // FW 13.x Investigate there is some sort of bitflag related to joycons
- INSERT_PADDING_BYTES(0x4);
- INSERT_PADDING_BYTES(0xc08); // 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{};
+ Core::HID::SixAxisSensorProperties sixaxis_fullkey_properties;
+ Core::HID::SixAxisSensorProperties sixaxis_handheld_properties;
+ Core::HID::SixAxisSensorProperties sixaxis_dual_left_properties;
+ Core::HID::SixAxisSensorProperties sixaxis_dual_right_properties;
+ Core::HID::SixAxisSensorProperties sixaxis_left_properties;
+ Core::HID::SixAxisSensorProperties sixaxis_right_properties;
+ INSERT_PADDING_BYTES(0xc06); // Unknown
};
static_assert(sizeof(NpadInternalState) == 0x5000, "NpadInternalState is an invalid size");
@@ -450,10 +482,19 @@ private:
std::chrono::steady_clock::time_point last_vibration_timepoint{};
};
+ struct SixaxisParameters {
+ bool is_fusion_enabled{true};
+ bool unaltered_passtrough{false};
+ Core::HID::SixAxisSensorFusionParameters fusion{};
+ Core::HID::SixAxisSensorCalibrationParameter calibration{};
+ Core::HID::SixAxisSensorIcInformation ic_information{};
+ GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard};
+ };
+
struct NpadControllerData {
- Core::HID::EmulatedController* device;
Kernel::KEvent* styleset_changed_event{};
- NpadInternalState shared_memory_entry{};
+ NpadInternalState* shared_memory = nullptr;
+ Core::HID::EmulatedController* device = nullptr;
std::array<VibrationData, 2> vibration{};
bool unintended_home_button_input_protection{};
@@ -466,9 +507,13 @@ 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{};
+ SixaxisParameters sixaxis_unknown{};
// Current pad state
NPadGenericState npad_pad_state{};
@@ -480,14 +525,14 @@ private:
SixAxisSensorState sixaxis_dual_right_state{};
SixAxisSensorState sixaxis_left_lifo_state{};
SixAxisSensorState sixaxis_right_lifo_state{};
- int callback_key;
+ int callback_key{};
};
void ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t controller_idx);
void InitNewlyAddedController(Core::HID::NpadIdType npad_id);
bool IsControllerSupported(Core::HID::NpadStyleIndex controller) const;
void RequestPadStateUpdate(Core::HID::NpadIdType npad_id);
- void WriteEmptyEntry(NpadInternalState& npad);
+ void WriteEmptyEntry(NpadInternalState* npad);
NpadControllerData& GetControllerFromHandle(
const Core::HID::SixAxisSensorHandle& device_handle);
@@ -500,9 +545,17 @@ private:
NpadControllerData& GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id);
const NpadControllerData& GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id) const;
+ Core::HID::SixAxisSensorProperties& GetSixaxisProperties(
+ const Core::HID::SixAxisSensorHandle& device_handle);
+ const Core::HID::SixAxisSensorProperties& GetSixaxisProperties(
+ const Core::HID::SixAxisSensorHandle& device_handle) const;
+ SixaxisParameters& GetSixaxisState(const Core::HID::SixAxisSensorHandle& device_handle);
+ const SixaxisParameters& GetSixaxisState(
+ const Core::HID::SixAxisSensorHandle& device_handle) const;
+
std::atomic<u64> press_state{};
- std::array<NpadControllerData, 10> controller_data{};
+ std::array<NpadControllerData, NPAD_COUNT> controller_data{};
KernelHelpers::ServiceContext& service_context;
std::mutex mutex;
std::vector<Core::HID::NpadIdType> supported_npad_id_types{};
@@ -510,7 +563,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/controllers/palma.cpp b/src/core/hle/service/hid/controllers/palma.cpp
new file mode 100644
index 000000000..575d4e626
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/palma.cpp
@@ -0,0 +1,229 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core_timing.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/service/hid/controllers/palma.h"
+#include "core/hle/service/kernel_helpers.h"
+
+namespace Service::HID {
+
+Controller_Palma::Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
+ KernelHelpers::ServiceContext& service_context_)
+ : ControllerBase{hid_core_}, service_context{service_context_} {
+ controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other);
+ operation_complete_event = service_context.CreateEvent("hid:PalmaOperationCompleteEvent");
+}
+
+Controller_Palma::~Controller_Palma() = default;
+
+void Controller_Palma::OnInit() {}
+
+void Controller_Palma::OnRelease() {}
+
+void Controller_Palma::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+ if (!IsControllerActivated()) {
+ return;
+ }
+}
+
+Result Controller_Palma::GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id,
+ PalmaConnectionHandle& handle) {
+ active_handle.npad_id = npad_id;
+ handle = active_handle;
+ return ResultSuccess;
+}
+
+Result Controller_Palma::InitializePalma(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ ActivateController();
+ return ResultSuccess;
+}
+
+Kernel::KReadableEvent& Controller_Palma::AcquirePalmaOperationCompleteEvent(
+ const PalmaConnectionHandle& handle) const {
+ if (handle.npad_id != active_handle.npad_id) {
+ LOG_ERROR(Service_HID, "Invalid npad id {}", handle.npad_id);
+ }
+ return operation_complete_event->GetReadableEvent();
+}
+
+Result Controller_Palma::GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
+ PalmaOperationType& operation_type,
+ PalmaOperationData& data) const {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation_type = operation.operation;
+ data = operation.data;
+ return ResultSuccess;
+}
+
+Result Controller_Palma::PlayPalmaActivity(const PalmaConnectionHandle& handle,
+ u64 palma_activity) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::PlayActivity;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::SetPalmaFrModeType(const PalmaConnectionHandle& handle,
+ PalmaFrModeType fr_mode_) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ fr_mode = fr_mode_;
+ return ResultSuccess;
+}
+
+Result Controller_Palma::ReadPalmaStep(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::ReadStep;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ return ResultSuccess;
+}
+
+Result Controller_Palma::ResetPalmaStep(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ return ResultSuccess;
+}
+
+void Controller_Palma::ReadPalmaApplicationSection() {}
+
+void Controller_Palma::WritePalmaApplicationSection() {}
+
+Result Controller_Palma::ReadPalmaUniqueCode(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::ReadUniqueCode;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::SetUniqueCodeInvalid;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+void Controller_Palma::WritePalmaActivityEntry() {}
+
+Result Controller_Palma::WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle,
+ u64 unknown) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::WriteRgbLedPatternEntry;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave,
+ u8* t_mem, u64 size) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::WriteWaveEntry;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
+ s32 database_id_version_) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ database_id_version = database_id_version_;
+ operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
+ operation.result = PalmaResultSuccess;
+ operation.data[0] = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::GetPalmaDataBaseIdentificationVersion(
+ const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation.data[0] = static_cast<u8>(database_id_version);
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+void Controller_Palma::SuspendPalmaFeature() {}
+
+Result Controller_Palma::GetPalmaOperationResult(const PalmaConnectionHandle& handle) const {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ return operation.result;
+}
+void Controller_Palma::ReadPalmaPlayLog() {}
+
+void Controller_Palma::ResetPalmaPlayLog() {}
+
+void Controller_Palma::SetIsPalmaAllConnectable(bool is_all_connectable) {
+ // If true controllers are able to be paired
+ is_connectable = is_all_connectable;
+}
+
+void Controller_Palma::SetIsPalmaPairedConnectable() {}
+
+Result Controller_Palma::PairPalma(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ // TODO: Do something
+ return ResultSuccess;
+}
+
+void Controller_Palma::SetPalmaBoostMode(bool boost_mode) {}
+
+void Controller_Palma::CancelWritePalmaWaveEntry() {}
+
+void Controller_Palma::EnablePalmaBoostMode() {}
+
+void Controller_Palma::GetPalmaBluetoothAddress() {}
+
+void Controller_Palma::SetDisallowedPalmaConnection() {}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/palma.h b/src/core/hle/service/hid/controllers/palma.h
new file mode 100644
index 000000000..1d7fc94e1
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/palma.h
@@ -0,0 +1,163 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+#include "core/hle/service/hid/errors.h"
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Service::KernelHelpers {
+class ServiceContext;
+}
+
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
+namespace Service::HID {
+class Controller_Palma final : public ControllerBase {
+public:
+ using PalmaOperationData = std::array<u8, 0x140>;
+
+ // This is nn::hid::PalmaOperationType
+ enum class PalmaOperationType {
+ PlayActivity,
+ SetFrModeType,
+ ReadStep,
+ EnableStep,
+ ResetStep,
+ ReadApplicationSection,
+ WriteApplicationSection,
+ ReadUniqueCode,
+ SetUniqueCodeInvalid,
+ WriteActivityEntry,
+ WriteRgbLedPatternEntry,
+ WriteWaveEntry,
+ ReadDataBaseIdentificationVersion,
+ WriteDataBaseIdentificationVersion,
+ SuspendFeature,
+ ReadPlayLog,
+ ResetPlayLog,
+ };
+
+ // This is nn::hid::PalmaWaveSet
+ enum class PalmaWaveSet : u64 {
+ Small,
+ Medium,
+ Large,
+ };
+
+ // This is nn::hid::PalmaFrModeType
+ enum class PalmaFrModeType : u64 {
+ Off,
+ B01,
+ B02,
+ B03,
+ Downloaded,
+ };
+
+ // This is nn::hid::PalmaFeature
+ enum class PalmaFeature : u64 {
+ FrMode,
+ RumbleFeedback,
+ Step,
+ MuteSwitch,
+ };
+
+ // This is nn::hid::PalmaOperationInfo
+ struct PalmaOperationInfo {
+ PalmaOperationType operation{};
+ Result result{PalmaResultSuccess};
+ PalmaOperationData data{};
+ };
+ static_assert(sizeof(PalmaOperationInfo) == 0x148, "PalmaOperationInfo is an invalid size");
+
+ // This is nn::hid::PalmaActivityEntry
+ struct PalmaActivityEntry {
+ u32 rgb_led_pattern_index;
+ INSERT_PADDING_BYTES(2);
+ PalmaWaveSet wave_set;
+ u32 wave_index;
+ INSERT_PADDING_BYTES(12);
+ };
+ static_assert(sizeof(PalmaActivityEntry) == 0x20, "PalmaActivityEntry is an invalid size");
+
+ struct PalmaConnectionHandle {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_BYTES(4); // Unknown
+ };
+ static_assert(sizeof(PalmaConnectionHandle) == 0x8,
+ "PalmaConnectionHandle has incorrect size.");
+
+ explicit Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
+ KernelHelpers::ServiceContext& service_context_);
+ ~Controller_Palma() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
+
+ Result GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id, PalmaConnectionHandle& handle);
+ Result InitializePalma(const PalmaConnectionHandle& handle);
+ Kernel::KReadableEvent& AcquirePalmaOperationCompleteEvent(
+ const PalmaConnectionHandle& handle) const;
+ Result GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
+ PalmaOperationType& operation_type,
+ PalmaOperationData& data) const;
+ Result PlayPalmaActivity(const PalmaConnectionHandle& handle, u64 palma_activity);
+ Result SetPalmaFrModeType(const PalmaConnectionHandle& handle, PalmaFrModeType fr_mode_);
+ Result ReadPalmaStep(const PalmaConnectionHandle& handle);
+ Result EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled);
+ Result ResetPalmaStep(const PalmaConnectionHandle& handle);
+ Result ReadPalmaUniqueCode(const PalmaConnectionHandle& handle);
+ Result SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle);
+ Result WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle, u64 unknown);
+ Result WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave, u8* t_mem,
+ u64 size);
+ Result SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
+ s32 database_id_version_);
+ Result GetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle);
+ Result GetPalmaOperationResult(const PalmaConnectionHandle& handle) const;
+ void SetIsPalmaAllConnectable(bool is_all_connectable);
+ Result PairPalma(const PalmaConnectionHandle& handle);
+ void SetPalmaBoostMode(bool boost_mode);
+
+private:
+ void ReadPalmaApplicationSection();
+ void WritePalmaApplicationSection();
+ void WritePalmaActivityEntry();
+ void SuspendPalmaFeature();
+ void ReadPalmaPlayLog();
+ void ResetPalmaPlayLog();
+ void SetIsPalmaPairedConnectable();
+ void CancelWritePalmaWaveEntry();
+ void EnablePalmaBoostMode();
+ void GetPalmaBluetoothAddress();
+ void SetDisallowedPalmaConnection();
+
+ bool is_connectable{};
+ s32 database_id_version{};
+ PalmaOperationInfo operation{};
+ PalmaFrModeType fr_mode{};
+ PalmaConnectionHandle active_handle{};
+
+ Core::HID::EmulatedController* controller;
+
+ Kernel::KEvent* operation_complete_event;
+ KernelHelpers::ServiceContext& service_context;
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp
index b7d7a5756..df9ee0c3f 100644
--- a/src/core/hle/service/hid/controllers/stubbed.cpp
+++ b/src/core/hle/service/hid/controllers/stubbed.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/common_types.h"
@@ -10,15 +9,18 @@
namespace Service::HID {
-Controller_Stubbed::Controller_Stubbed(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} {}
+Controller_Stubbed::Controller_Stubbed(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
+ : ControllerBase{hid_core_} {
+ raw_shared_memory = raw_shared_memory_;
+}
+
Controller_Stubbed::~Controller_Stubbed() = default;
void Controller_Stubbed::OnInit() {}
void Controller_Stubbed::OnRelease() {}
-void Controller_Stubbed::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
- std::size_t size) {
+void Controller_Stubbed::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!smart_update) {
return;
}
@@ -29,7 +31,7 @@ void Controller_Stubbed::OnUpdate(const Core::Timing::CoreTiming& core_timing, u
header.entry_count = 0;
header.last_entry_index = 0;
- std::memcpy(data + common_offset, &header, sizeof(CommonHeader));
+ std::memcpy(raw_shared_memory + common_offset, &header, sizeof(CommonHeader));
}
void Controller_Stubbed::SetCommonHeaderOffset(std::size_t off) {
diff --git a/src/core/hle/service/hid/controllers/stubbed.h b/src/core/hle/service/hid/controllers/stubbed.h
index 0044a4efa..1483a968e 100644
--- a/src/core/hle/service/hid/controllers/stubbed.h
+++ b/src/core/hle/service/hid/controllers/stubbed.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,7 +9,7 @@
namespace Service::HID {
class Controller_Stubbed final : public ControllerBase {
public:
- explicit Controller_Stubbed(Core::HID::HIDCore& hid_core_);
+ explicit Controller_Stubbed(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
~Controller_Stubbed() override;
// Called when the controller is initialized
@@ -20,19 +19,20 @@ public:
void OnRelease() override;
// When the controller is requesting an update for the shared memory
- void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
void SetCommonHeaderOffset(std::size_t off);
private:
struct CommonHeader {
- s64 timestamp;
- s64 total_entry_count;
- s64 last_entry_index;
- s64 entry_count;
+ s64 timestamp{};
+ s64 total_entry_count{};
+ s64 last_entry_index{};
+ s64 entry_count{};
};
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
+ u8* raw_shared_memory = nullptr;
bool smart_update{};
std::size_t common_offset{};
};
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
index 48978e5c6..1da8d3eb0 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.cpp
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -1,11 +1,9 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
#include "common/common_types.h"
-#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
@@ -17,8 +15,13 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
-Controller_Touchscreen::Controller_Touchscreen(Core::HID::HIDCore& hid_core_)
+Controller_Touchscreen::Controller_Touchscreen(Core::HID::HIDCore& hid_core_,
+ u8* raw_shared_memory_)
: ControllerBase{hid_core_} {
+ static_assert(SHARED_MEMORY_OFFSET + sizeof(TouchSharedMemory) < shared_memory_size,
+ "TouchSharedMemory is bigger than the shared memory");
+ shared_memory = std::construct_at(
+ reinterpret_cast<TouchSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET));
console = hid_core.GetEmulatedConsole();
}
@@ -28,14 +31,12 @@ void Controller_Touchscreen::OnInit() {}
void Controller_Touchscreen::OnRelease() {}
-void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
- std::size_t size) {
- touch_screen_lifo.timestamp = core_timing.GetCPUTicks();
+void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+ shared_memory->touch_screen_lifo.timestamp = core_timing.GetCPUTicks();
if (!IsControllerActivated()) {
- touch_screen_lifo.buffer_count = 0;
- touch_screen_lifo.buffer_tail = 0;
- std::memcpy(data, &touch_screen_lifo, sizeof(touch_screen_lifo));
+ shared_memory->touch_screen_lifo.buffer_count = 0;
+ shared_memory->touch_screen_lifo.buffer_tail = 0;
return;
}
@@ -43,7 +44,6 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
for (std::size_t id = 0; id < MAX_FINGERS; id++) {
const auto& current_touch = touch_status[id];
auto& finger = fingers[id];
- finger.position = current_touch.position;
finger.id = current_touch.id;
if (finger.attribute.start_touch) {
@@ -60,13 +60,18 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
if (!finger.pressed && current_touch.pressed) {
finger.attribute.start_touch.Assign(1);
finger.pressed = true;
+ finger.position = current_touch.position;
continue;
}
if (finger.pressed && !current_touch.pressed) {
finger.attribute.raw = 0;
finger.attribute.end_touch.Assign(1);
+ continue;
}
+
+ // Only update position if touch is not on a special frame
+ finger.position = current_touch.position;
}
std::array<Core::HID::TouchFinger, MAX_FINGERS> active_fingers;
@@ -76,7 +81,7 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
static_cast<std::size_t>(std::distance(active_fingers.begin(), end_iter));
const u64 tick = core_timing.GetCPUTicks();
- const auto& last_entry = touch_screen_lifo.ReadCurrentEntry().state;
+ const auto& last_entry = shared_memory->touch_screen_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
next_state.entry_count = static_cast<s32>(active_fingers_count);
@@ -108,8 +113,7 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
}
}
- touch_screen_lifo.WriteNextEntry(next_state);
- std::memcpy(data + SHARED_MEMORY_OFFSET, &touch_screen_lifo, sizeof(touch_screen_lifo));
+ shared_memory->touch_screen_lifo.WriteNextEntry(next_state);
}
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index 708dde4f0..e57a3a80e 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -1,14 +1,10 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
-#include "common/point.h"
-#include "common/swap.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
@@ -29,14 +25,14 @@ public:
// This is nn::hid::TouchScreenConfigurationForNx
struct TouchScreenConfigurationForNx {
- TouchScreenModeForNx mode;
+ TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting};
INSERT_PADDING_BYTES_NOINIT(0x7);
INSERT_PADDING_BYTES_NOINIT(0xF); // Reserved
};
static_assert(sizeof(TouchScreenConfigurationForNx) == 0x17,
"TouchScreenConfigurationForNx is an invalid size");
- explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_);
+ explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
~Controller_Touchscreen() override;
// Called when the controller is initialized
@@ -46,26 +42,32 @@ public:
void OnRelease() override;
// When the controller is requesting an update for the shared memory
- void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
private:
static constexpr std::size_t MAX_FINGERS = 16;
// This is nn::hid::TouchScreenState
struct TouchScreenState {
- s64 sampling_number;
- s32 entry_count;
+ s64 sampling_number{};
+ s32 entry_count{};
INSERT_PADDING_BYTES(4); // Reserved
- std::array<Core::HID::TouchState, MAX_FINGERS> states;
+ std::array<Core::HID::TouchState, MAX_FINGERS> states{};
};
static_assert(sizeof(TouchScreenState) == 0x290, "TouchScreenState is an invalid size");
- // This is nn::hid::detail::TouchScreenLifo
- Lifo<TouchScreenState, hid_entry_count> touch_screen_lifo{};
- static_assert(sizeof(touch_screen_lifo) == 0x2C38, "touch_screen_lifo is an invalid size");
+ struct TouchSharedMemory {
+ // This is nn::hid::detail::TouchScreenLifo
+ Lifo<TouchScreenState, hid_entry_count> touch_screen_lifo{};
+ static_assert(sizeof(touch_screen_lifo) == 0x2C38, "touch_screen_lifo is an invalid size");
+ INSERT_PADDING_WORDS(0xF2);
+ };
+ static_assert(sizeof(TouchSharedMemory) == 0x3000, "TouchSharedMemory is an invalid size");
+
TouchScreenState next_state{};
+ TouchSharedMemory* shared_memory = nullptr;
+ Core::HID::EmulatedConsole* console = nullptr;
- std::array<Core::HID::TouchFinger, MAX_FINGERS> fingers;
- Core::HID::EmulatedConsole* console;
+ std::array<Core::HID::TouchFinger, MAX_FINGERS> fingers{};
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp
index e4da16466..62119e2c5 100644
--- a/src/core/hle/service/hid/controllers/xpad.cpp
+++ b/src/core/hle/service/hid/controllers/xpad.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/common_types.h"
@@ -11,28 +10,31 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00;
-Controller_XPad::Controller_XPad(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} {}
+Controller_XPad::Controller_XPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
+ : ControllerBase{hid_core_} {
+ static_assert(SHARED_MEMORY_OFFSET + sizeof(XpadSharedMemory) < shared_memory_size,
+ "XpadSharedMemory is bigger than the shared memory");
+ shared_memory = std::construct_at(
+ reinterpret_cast<XpadSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET));
+}
Controller_XPad::~Controller_XPad() = default;
void Controller_XPad::OnInit() {}
void Controller_XPad::OnRelease() {}
-void Controller_XPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
- std::size_t size) {
+void Controller_XPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
- basic_xpad_lifo.buffer_count = 0;
- basic_xpad_lifo.buffer_tail = 0;
- std::memcpy(data + SHARED_MEMORY_OFFSET, &basic_xpad_lifo, sizeof(basic_xpad_lifo));
+ shared_memory->basic_xpad_lifo.buffer_count = 0;
+ shared_memory->basic_xpad_lifo.buffer_tail = 0;
return;
}
- const auto& last_entry = basic_xpad_lifo.ReadCurrentEntry().state;
+ const auto& last_entry = shared_memory->basic_xpad_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
// TODO(ogniK): Update xpad states
- basic_xpad_lifo.WriteNextEntry(next_state);
- std::memcpy(data + SHARED_MEMORY_OFFSET, &basic_xpad_lifo, sizeof(basic_xpad_lifo));
+ shared_memory->basic_xpad_lifo.WriteNextEntry(next_state);
}
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h
index ba8db8d9d..d01dee5fc 100644
--- a/src/core/hle/service/hid/controllers/xpad.h
+++ b/src/core/hle/service/hid/controllers/xpad.h
@@ -1,13 +1,10 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/bit_field.h"
-#include "common/common_funcs.h"
#include "common/common_types.h"
-#include "common/swap.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
@@ -15,7 +12,7 @@
namespace Service::HID {
class Controller_XPad final : public ControllerBase {
public:
- explicit Controller_XPad(Core::HID::HIDCore& hid_core_);
+ explicit Controller_XPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
~Controller_XPad() override;
// Called when the controller is initialized
@@ -25,7 +22,7 @@ public:
void OnRelease() override;
// When the controller is requesting an update for the shared memory
- void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
private:
// This is nn::hid::BasicXpadAttributeSet
@@ -93,17 +90,23 @@ private:
// This is nn::hid::detail::BasicXpadState
struct BasicXpadState {
- s64 sampling_number;
- BasicXpadAttributeSet attributes;
- BasicXpadButtonSet pad_states;
- Core::HID::AnalogStickState l_stick;
- Core::HID::AnalogStickState r_stick;
+ s64 sampling_number{};
+ BasicXpadAttributeSet attributes{};
+ BasicXpadButtonSet pad_states{};
+ Core::HID::AnalogStickState l_stick{};
+ Core::HID::AnalogStickState r_stick{};
};
static_assert(sizeof(BasicXpadState) == 0x20, "BasicXpadState is an invalid size");
- // This is nn::hid::detail::BasicXpadLifo
- Lifo<BasicXpadState, hid_entry_count> basic_xpad_lifo{};
- static_assert(sizeof(basic_xpad_lifo) == 0x2C8, "basic_xpad_lifo is an invalid size");
+ struct XpadSharedMemory {
+ // This is nn::hid::detail::BasicXpadLifo
+ Lifo<BasicXpadState, hid_entry_count> basic_xpad_lifo{};
+ static_assert(sizeof(basic_xpad_lifo) == 0x2C8, "basic_xpad_lifo is an invalid size");
+ INSERT_PADDING_WORDS(0x4E);
+ };
+ static_assert(sizeof(XpadSharedMemory) == 0x400, "XpadSharedMemory is an invalid size");
+
BasicXpadState next_state{};
+ XpadSharedMemory* shared_memory = nullptr;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h
index 3583642e7..76208e9a4 100644
--- a/src/core/hle/service/hid/errors.h
+++ b/src/core/hle/service/hid/errors.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,6 +7,24 @@
namespace Service::HID {
-constexpr ResultCode ERR_NPAD_NOT_CONNECTED{ErrorModule::HID, 710};
+constexpr Result PalmaResultSuccess{ErrorModule::HID, 0};
+constexpr Result NpadInvalidHandle{ErrorModule::HID, 100};
+constexpr Result NpadDeviceIndexOutOfRange{ErrorModule::HID, 107};
+constexpr Result VibrationInvalidStyleIndex{ErrorModule::HID, 122};
+constexpr Result VibrationInvalidNpadId{ErrorModule::HID, 123};
+constexpr Result VibrationDeviceIndexOutOfRange{ErrorModule::HID, 124};
+constexpr Result InvalidSixAxisFusionRange{ErrorModule::HID, 423};
+constexpr Result NpadIsDualJoycon{ErrorModule::HID, 601};
+constexpr Result NpadIsSameType{ErrorModule::HID, 602};
+constexpr Result InvalidNpadId{ErrorModule::HID, 709};
+constexpr Result NpadNotConnected{ErrorModule::HID, 710};
+constexpr Result InvalidPalmaHandle{ErrorModule::HID, 3302};
} // namespace Service::HID
+
+namespace Service::IRS {
+
+constexpr Result InvalidProcessorState{ErrorModule::Irsensor, 78};
+constexpr Result InvalidIrCameraHandle{ErrorModule::Irsensor, 204};
+
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index d9202ea6c..46bad7871 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include "common/common_types.h"
@@ -16,6 +15,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"
@@ -27,6 +27,7 @@
#include "core/hle/service/hid/controllers/keyboard.h"
#include "core/hle/service/hid/controllers/mouse.h"
#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/controllers/palma.h"
#include "core/hle/service/hid/controllers/stubbed.h"
#include "core/hle/service/hid/controllers/touchscreen.h"
#include "core/hle/service/hid/controllers/xpad.h"
@@ -35,11 +36,10 @@ namespace Service::HID {
// Updating period for each HID device.
// Period time is obtained by measuring the number of samples in a second on HW using a homebrew
-constexpr auto pad_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 250Hz)
+// Correct pad_update_ns is 4ms this is overclocked to lower input lag
+constexpr auto pad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000}; // (1ms, 1000Hz)
constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz)
-// TODO: Correct update rate for motion is 5ms. Check why some games don't behave at that speed
-constexpr auto motion_update_ns = std::chrono::nanoseconds{10 * 1000 * 1000}; // (10ms, 100Hz)
-constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
+constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz)
IAppletResource::IAppletResource(Core::System& system_,
KernelHelpers::ServiceContext& service_context_)
@@ -48,20 +48,21 @@ IAppletResource::IAppletResource(Core::System& system_,
{0, &IAppletResource::GetSharedMemoryHandle, "GetSharedMemoryHandle"},
};
RegisterHandlers(functions);
-
- MakeController<Controller_DebugPad>(HidController::DebugPad);
- MakeController<Controller_Touchscreen>(HidController::Touchscreen);
- MakeController<Controller_Mouse>(HidController::Mouse);
- MakeController<Controller_Keyboard>(HidController::Keyboard);
- MakeController<Controller_XPad>(HidController::XPad);
- MakeController<Controller_Stubbed>(HidController::HomeButton);
- MakeController<Controller_Stubbed>(HidController::SleepButton);
- MakeController<Controller_Stubbed>(HidController::CaptureButton);
- MakeController<Controller_Stubbed>(HidController::InputDetector);
- MakeController<Controller_Stubbed>(HidController::UniquePad);
- MakeControllerWithServiceContext<Controller_NPad>(HidController::NPad);
- MakeController<Controller_Gesture>(HidController::Gesture);
- MakeController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor);
+ u8* shared_memory = system.Kernel().GetHidSharedMem().GetPointer();
+ MakeController<Controller_DebugPad>(HidController::DebugPad, shared_memory);
+ MakeController<Controller_Touchscreen>(HidController::Touchscreen, shared_memory);
+ MakeController<Controller_Mouse>(HidController::Mouse, shared_memory);
+ MakeController<Controller_Keyboard>(HidController::Keyboard, shared_memory);
+ MakeController<Controller_XPad>(HidController::XPad, shared_memory);
+ MakeController<Controller_Stubbed>(HidController::HomeButton, shared_memory);
+ MakeController<Controller_Stubbed>(HidController::SleepButton, shared_memory);
+ MakeController<Controller_Stubbed>(HidController::CaptureButton, shared_memory);
+ MakeController<Controller_Stubbed>(HidController::InputDetector, shared_memory);
+ MakeController<Controller_Stubbed>(HidController::UniquePad, shared_memory);
+ MakeControllerWithServiceContext<Controller_NPad>(HidController::NPad, shared_memory);
+ MakeController<Controller_Gesture>(HidController::Gesture, shared_memory);
+ MakeController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor, shared_memory);
+ MakeControllerWithServiceContext<Controller_Palma>(HidController::Palma, shared_memory);
// Homebrew doesn't try to activate some controllers, so we activate them by default
GetController<Controller_NPad>(HidController::NPad).ActivateController();
@@ -76,26 +77,34 @@ IAppletResource::IAppletResource(Core::System& system_,
// Register update callbacks
pad_update_event = Core::Timing::CreateEvent(
"HID::UpdatePadCallback",
- [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ [this](std::uintptr_t user_data, s64 time,
+ std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
UpdateControllers(user_data, ns_late);
+ return std::nullopt;
});
mouse_keyboard_update_event = Core::Timing::CreateEvent(
"HID::UpdateMouseKeyboardCallback",
- [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ [this](std::uintptr_t user_data, s64 time,
+ std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
UpdateMouseKeyboard(user_data, ns_late);
+ return std::nullopt;
});
motion_update_event = Core::Timing::CreateEvent(
"HID::UpdateMotionCallback",
- [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ [this](std::uintptr_t user_data, s64 time,
+ std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
UpdateMotion(user_data, ns_late);
+ return std::nullopt;
});
- system.CoreTiming().ScheduleEvent(pad_update_ns, pad_update_event);
- system.CoreTiming().ScheduleEvent(mouse_keyboard_update_ns, mouse_keyboard_update_event);
- system.CoreTiming().ScheduleEvent(motion_update_ns, motion_update_event);
+ system.CoreTiming().ScheduleLoopingEvent(pad_update_ns, pad_update_ns, pad_update_event);
+ system.CoreTiming().ScheduleLoopingEvent(mouse_keyboard_update_ns, mouse_keyboard_update_ns,
+ mouse_keyboard_update_event);
+ system.CoreTiming().ScheduleLoopingEvent(motion_update_ns, motion_update_ns,
+ motion_update_event);
system.HIDCore().ReloadInputDevices();
}
@@ -135,47 +144,22 @@ void IAppletResource::UpdateControllers(std::uintptr_t user_data,
if (controller == controllers[static_cast<size_t>(HidController::Mouse)]) {
continue;
}
- controller->OnUpdate(core_timing, system.Kernel().GetHidSharedMem().GetPointer(),
- SHARED_MEMORY_SIZE);
- }
-
- // If ns_late is higher than the update rate ignore the delay
- if (ns_late > pad_update_ns) {
- ns_late = {};
+ controller->OnUpdate(core_timing);
}
-
- core_timing.ScheduleEvent(pad_update_ns - ns_late, pad_update_event);
}
void IAppletResource::UpdateMouseKeyboard(std::uintptr_t user_data,
std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
- controllers[static_cast<size_t>(HidController::Mouse)]->OnUpdate(
- core_timing, system.Kernel().GetHidSharedMem().GetPointer(), SHARED_MEMORY_SIZE);
- controllers[static_cast<size_t>(HidController::Keyboard)]->OnUpdate(
- core_timing, system.Kernel().GetHidSharedMem().GetPointer(), SHARED_MEMORY_SIZE);
-
- // If ns_late is higher than the update rate ignore the delay
- if (ns_late > mouse_keyboard_update_ns) {
- ns_late = {};
- }
-
- core_timing.ScheduleEvent(mouse_keyboard_update_ns - ns_late, mouse_keyboard_update_event);
+ controllers[static_cast<size_t>(HidController::Mouse)]->OnUpdate(core_timing);
+ controllers[static_cast<size_t>(HidController::Keyboard)]->OnUpdate(core_timing);
}
void IAppletResource::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
- controllers[static_cast<size_t>(HidController::NPad)]->OnMotionUpdate(
- core_timing, system.Kernel().GetHidSharedMem().GetPointer(), SHARED_MEMORY_SIZE);
-
- // If ns_late is higher than the update rate ignore the delay
- if (ns_late > motion_update_ns) {
- ns_late = {};
- }
-
- core_timing.ScheduleEvent(motion_update_ns - ns_late, motion_update_event);
+ controllers[static_cast<size_t>(HidController::NPad)]->OnMotionUpdate(core_timing);
}
class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
@@ -247,7 +231,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"},
@@ -263,12 +247,12 @@ Hid::Hid(Core::System& system_)
{81, &Hid::ResetGyroscopeZeroDriftMode, "ResetGyroscopeZeroDriftMode"},
{82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"},
{83, &Hid::IsFirmwareUpdateAvailableForSixAxisSensor, "IsFirmwareUpdateAvailableForSixAxisSensor"},
- {84, nullptr, "EnableSixAxisSensorUnalteredPassthrough"},
- {85, nullptr, "IsSixAxisSensorUnalteredPassthroughEnabled"},
+ {84, &Hid::EnableSixAxisSensorUnalteredPassthrough, "EnableSixAxisSensorUnalteredPassthrough"},
+ {85, &Hid::IsSixAxisSensorUnalteredPassthroughEnabled, "IsSixAxisSensorUnalteredPassthroughEnabled"},
{86, nullptr, "StoreSixAxisSensorCalibrationParameter"},
- {87, nullptr, "LoadSixAxisSensorCalibrationParameter"},
- {88, nullptr, "GetSixAxisSensorIcInformation"},
- {89, nullptr, "ResetIsSixAxisSensorDeviceNewlyAssigned"},
+ {87, &Hid::LoadSixAxisSensorCalibrationParameter, "LoadSixAxisSensorCalibrationParameter"},
+ {88, &Hid::GetSixAxisSensorIcInformation, "GetSixAxisSensorIcInformation"},
+ {89, &Hid::ResetIsSixAxisSensorDeviceNewlyAssigned, "ResetIsSixAxisSensorDeviceNewlyAssigned"},
{91, &Hid::ActivateGesture, "ActivateGesture"},
{100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"},
{101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"},
@@ -329,40 +313,40 @@ Hid::Hid(Core::System& system_)
{406, nullptr, "GetNpadLeftRightInterfaceType"},
{407, nullptr, "GetNpadOfHighestBatteryLevel"},
{408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"},
- {500, nullptr, "GetPalmaConnectionHandle"},
- {501, nullptr, "InitializePalma"},
- {502, nullptr, "AcquirePalmaOperationCompleteEvent"},
- {503, nullptr, "GetPalmaOperationInfo"},
- {504, nullptr, "PlayPalmaActivity"},
- {505, nullptr, "SetPalmaFrModeType"},
- {506, nullptr, "ReadPalmaStep"},
- {507, nullptr, "EnablePalmaStep"},
- {508, nullptr, "ResetPalmaStep"},
- {509, nullptr, "ReadPalmaApplicationSection"},
- {510, nullptr, "WritePalmaApplicationSection"},
- {511, nullptr, "ReadPalmaUniqueCode"},
- {512, nullptr, "SetPalmaUniqueCodeInvalid"},
- {513, nullptr, "WritePalmaActivityEntry"},
- {514, nullptr, "WritePalmaRgbLedPatternEntry"},
- {515, nullptr, "WritePalmaWaveEntry"},
- {516, nullptr, "SetPalmaDataBaseIdentificationVersion"},
- {517, nullptr, "GetPalmaDataBaseIdentificationVersion"},
- {518, nullptr, "SuspendPalmaFeature"},
- {519, nullptr, "GetPalmaOperationResult"},
- {520, nullptr, "ReadPalmaPlayLog"},
- {521, nullptr, "ResetPalmaPlayLog"},
+ {500, &Hid::GetPalmaConnectionHandle, "GetPalmaConnectionHandle"},
+ {501, &Hid::InitializePalma, "InitializePalma"},
+ {502, &Hid::AcquirePalmaOperationCompleteEvent, "AcquirePalmaOperationCompleteEvent"},
+ {503, &Hid::GetPalmaOperationInfo, "GetPalmaOperationInfo"},
+ {504, &Hid::PlayPalmaActivity, "PlayPalmaActivity"},
+ {505, &Hid::SetPalmaFrModeType, "SetPalmaFrModeType"},
+ {506, &Hid::ReadPalmaStep, "ReadPalmaStep"},
+ {507, &Hid::EnablePalmaStep, "EnablePalmaStep"},
+ {508, &Hid::ResetPalmaStep, "ResetPalmaStep"},
+ {509, &Hid::ReadPalmaApplicationSection, "ReadPalmaApplicationSection"},
+ {510, &Hid::WritePalmaApplicationSection, "WritePalmaApplicationSection"},
+ {511, &Hid::ReadPalmaUniqueCode, "ReadPalmaUniqueCode"},
+ {512, &Hid::SetPalmaUniqueCodeInvalid, "SetPalmaUniqueCodeInvalid"},
+ {513, &Hid::WritePalmaActivityEntry, "WritePalmaActivityEntry"},
+ {514, &Hid::WritePalmaRgbLedPatternEntry, "WritePalmaRgbLedPatternEntry"},
+ {515, &Hid::WritePalmaWaveEntry, "WritePalmaWaveEntry"},
+ {516, &Hid::SetPalmaDataBaseIdentificationVersion, "SetPalmaDataBaseIdentificationVersion"},
+ {517, &Hid::GetPalmaDataBaseIdentificationVersion, "GetPalmaDataBaseIdentificationVersion"},
+ {518, &Hid::SuspendPalmaFeature, "SuspendPalmaFeature"},
+ {519, &Hid::GetPalmaOperationResult, "GetPalmaOperationResult"},
+ {520, &Hid::ReadPalmaPlayLog, "ReadPalmaPlayLog"},
+ {521, &Hid::ResetPalmaPlayLog, "ResetPalmaPlayLog"},
{522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"},
- {523, nullptr, "SetIsPalmaPairedConnectable"},
- {524, nullptr, "PairPalma"},
+ {523, &Hid::SetIsPalmaPairedConnectable, "SetIsPalmaPairedConnectable"},
+ {524, &Hid::PairPalma, "PairPalma"},
{525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"},
- {526, nullptr, "CancelWritePalmaWaveEntry"},
- {527, nullptr, "EnablePalmaBoostMode"},
- {528, nullptr, "GetPalmaBluetoothAddress"},
- {529, nullptr, "SetDisallowedPalmaConnection"},
+ {526, &Hid::CancelWritePalmaWaveEntry, "CancelWritePalmaWaveEntry"},
+ {527, &Hid::EnablePalmaBoostMode, "EnablePalmaBoostMode"},
+ {528, &Hid::GetPalmaBluetoothAddress, "GetPalmaBluetoothAddress"},
+ {529, &Hid::SetDisallowedPalmaConnection, "SetDisallowedPalmaConnection"},
{1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"},
{1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"},
{1002, &Hid::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"},
- {1003, nullptr, "IsFirmwareUpdateNeededForNotification"},
+ {1003, &Hid::IsFirmwareUpdateNeededForNotification, "IsFirmwareUpdateNeededForNotification"},
{2000, nullptr, "ActivateDigitizer"},
};
// clang-format on
@@ -527,8 +511,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 +520,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 +534,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 +543,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 +584,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 +596,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 +611,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 +623,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 +637,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 +649,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 +664,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,7 +680,11 @@ void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ if (result1.IsError()) {
+ rb.Push(result1);
+ return;
+ }
+ rb.Push(result2);
}
void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
@@ -669,8 +693,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 +703,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 +717,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 +741,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 +752,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,6 +766,10 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
+ bool is_at_rest{};
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ 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,
@@ -746,8 +777,7 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .IsSixAxisSensorAtRest(parameters.sixaxis_handle));
+ rb.Push(is_at_rest);
}
void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& ctx) {
@@ -761,6 +791,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);
+ controller.IsFirmwareUpdateAvailableForSixAxisSensor(parameters.sixaxis_handle,
+ is_firmware_available);
+
LOG_WARNING(
Service_HID,
"(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
@@ -769,7 +804,145 @@ void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& c
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(false);
+ rb.Push(is_firmware_available);
+}
+
+void Hid::EnableSixAxisSensorUnalteredPassthrough(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool enabled;
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.EnableSixAxisSensorUnalteredPassthrough(
+ parameters.sixaxis_handle, parameters.enabled);
+
+ LOG_DEBUG(Service_HID,
+ "(STUBBED) called, enabled={}, npad_type={}, npad_id={}, device_index={}, "
+ "applet_resource_user_id={}",
+ parameters.enabled, parameters.sixaxis_handle.npad_type,
+ parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index,
+ parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::IsSixAxisSensorUnalteredPassthroughEnabled(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_unaltered_sisxaxis_enabled{};
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.IsSixAxisSensorUnalteredPassthroughEnabled(
+ parameters.sixaxis_handle, is_unaltered_sisxaxis_enabled);
+
+ LOG_DEBUG(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(is_unaltered_sisxaxis_enabled);
+}
+
+void Hid::LoadSixAxisSensorCalibrationParameter(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>()};
+
+ Core::HID::SixAxisSensorCalibrationParameter calibration{};
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result =
+ controller.LoadSixAxisSensorCalibrationParameter(parameters.sixaxis_handle, calibration);
+
+ LOG_WARNING(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(calibration);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::GetSixAxisSensorIcInformation(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>()};
+
+ Core::HID::SixAxisSensorIcInformation ic_information{};
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result =
+ controller.GetSixAxisSensorIcInformation(parameters.sixaxis_handle, ic_information);
+
+ LOG_WARNING(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(ic_information);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::ResetIsSixAxisSensorDeviceNewlyAssigned(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>()};
+
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result =
+ controller.ResetIsSixAxisSensorDeviceNewlyAssigned(parameters.sixaxis_handle);
+
+ LOG_WARNING(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
}
void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) {
@@ -878,6 +1051,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)
@@ -895,8 +1072,8 @@ void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .DisconnectNpad(parameters.npad_id);
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ controller.DisconnectNpad(parameters.npad_id);
LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id,
parameters.applet_resource_user_id);
@@ -909,13 +1086,15 @@ void Hid::GetPlayerLedPattern(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto npad_id{rp.PopEnum<Core::HID::NpadIdType>()};
+ Core::HID::LedPattern pattern{0, 0, 0, 0};
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.GetLedPattern(npad_id, pattern);
+
LOG_DEBUG(Service_HID, "called, npad_id={}", npad_id);
IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetLedPattern(npad_id)
- .raw);
+ rb.Push(result);
+ rb.Push(pattern.raw);
}
void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
@@ -975,9 +1154,9 @@ void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx
const auto parameters{rp.PopRaw<Parameters>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetNpadMode(parameters.npad_id, Controller_NPad::NpadJoyDeviceType::Left,
- Controller_NPad::NpadJoyAssignmentMode::Single);
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ controller.SetNpadMode(parameters.npad_id, Controller_NPad::NpadJoyDeviceType::Left,
+ Controller_NPad::NpadJoyAssignmentMode::Single);
LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id,
parameters.applet_resource_user_id);
@@ -998,9 +1177,9 @@ void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetNpadMode(parameters.npad_id, parameters.npad_joy_device_type,
- Controller_NPad::NpadJoyAssignmentMode::Single);
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ controller.SetNpadMode(parameters.npad_id, parameters.npad_joy_device_type,
+ Controller_NPad::NpadJoyAssignmentMode::Single);
LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}",
parameters.npad_id, parameters.applet_resource_user_id,
@@ -1021,8 +1200,8 @@ void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetNpadMode(parameters.npad_id, {}, Controller_NPad::NpadJoyAssignmentMode::Dual);
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ controller.SetNpadMode(parameters.npad_id, {}, Controller_NPad::NpadJoyAssignmentMode::Dual);
LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id,
parameters.applet_resource_user_id);
@@ -1037,14 +1216,14 @@ void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
const auto npad_id_2{rp.PopEnum<Core::HID::NpadIdType>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2);
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2);
LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}",
npad_id_1, npad_id_2, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void Hid::StartLrAssignmentMode(Kernel::HLERequestContext& ctx) {
@@ -1104,19 +1283,14 @@ void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) {
const auto npad_id_2{rp.PopEnum<Core::HID::NpadIdType>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- const bool res = applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SwapNpadAssignment(npad_id_1, npad_id_2);
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.SwapNpadAssignment(npad_id_1, npad_id_2);
LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}",
npad_id_1, npad_id_2, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
- if (res) {
- rb.Push(ResultSuccess);
- } else {
- LOG_ERROR(Service_HID, "Npads are not connected!");
- rb.Push(ERR_NPAD_NOT_CONNECTED);
- }
+ rb.Push(result);
}
void Hid::IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx) {
@@ -1130,13 +1304,17 @@ void Hid::IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext
const auto parameters{rp.PopRaw<Parameters>()};
+ bool is_enabled = false;
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result =
+ controller.IsUnintendedHomeButtonInputProtectionEnabled(parameters.npad_id, is_enabled);
+
LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
parameters.npad_id, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .IsUnintendedHomeButtonInputProtectionEnabled(parameters.npad_id));
+ rb.Push(result);
+ rb.Push(is_enabled);
}
void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx) {
@@ -1151,9 +1329,9 @@ void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& c
const auto parameters{rp.PopRaw<Parameters>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetUnintendedHomeButtonInputProtectionEnabled(
- parameters.unintended_home_button_input_protection, parameters.npad_id);
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.SetUnintendedHomeButtonInputProtectionEnabled(
+ parameters.unintended_home_button_input_protection, parameters.npad_id);
LOG_WARNING(Service_HID,
"(STUBBED) called, unintended_home_button_input_protection={}, npad_id={},"
@@ -1162,7 +1340,7 @@ void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& c
parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void Hid::SetNpadAnalogStickUseCenterClamp(Kernel::HLERequestContext& ctx) {
@@ -1222,8 +1400,11 @@ void Hid::ClearNpadCaptureButtonAssignment(Kernel::HLERequestContext& ctx) {
void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()};
+ const auto& controller =
+ GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
Core::HID::VibrationDeviceInfo vibration_device_info;
+ bool check_device_index = false;
switch (vibration_device_handle.npad_type) {
case Core::HID::NpadStyleIndex::ProController:
@@ -1231,34 +1412,46 @@ void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
case Core::HID::NpadStyleIndex::JoyconDual:
case Core::HID::NpadStyleIndex::JoyconLeft:
case Core::HID::NpadStyleIndex::JoyconRight:
- default:
vibration_device_info.type = Core::HID::VibrationDeviceType::LinearResonantActuator;
+ check_device_index = true;
break;
case Core::HID::NpadStyleIndex::GameCube:
vibration_device_info.type = Core::HID::VibrationDeviceType::GcErm;
break;
- case Core::HID::NpadStyleIndex::Pokeball:
+ case Core::HID::NpadStyleIndex::N64:
+ vibration_device_info.type = Core::HID::VibrationDeviceType::N64;
+ break;
+ default:
vibration_device_info.type = Core::HID::VibrationDeviceType::Unknown;
break;
}
- switch (vibration_device_handle.device_index) {
- case Core::HID::DeviceIndex::Left:
- vibration_device_info.position = Core::HID::VibrationDevicePosition::Left;
- break;
- case Core::HID::DeviceIndex::Right:
- vibration_device_info.position = Core::HID::VibrationDevicePosition::Right;
- break;
- case Core::HID::DeviceIndex::None:
- default:
- UNREACHABLE_MSG("DeviceIndex should never be None!");
- vibration_device_info.position = Core::HID::VibrationDevicePosition::None;
- break;
+ vibration_device_info.position = Core::HID::VibrationDevicePosition::None;
+ if (check_device_index) {
+ switch (vibration_device_handle.device_index) {
+ case Core::HID::DeviceIndex::Left:
+ vibration_device_info.position = Core::HID::VibrationDevicePosition::Left;
+ break;
+ case Core::HID::DeviceIndex::Right:
+ vibration_device_info.position = Core::HID::VibrationDevicePosition::Right;
+ break;
+ case Core::HID::DeviceIndex::None:
+ default:
+ ASSERT_MSG(false, "DeviceIndex should never be None!");
+ break;
+ }
}
LOG_DEBUG(Service_HID, "called, vibration_device_type={}, vibration_device_position={}",
vibration_device_info.type, vibration_device_info.position);
+ const auto result = controller.IsDeviceHandleValid(vibration_device_handle);
+ if (result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
rb.PushRaw(vibration_device_info);
@@ -1324,6 +1517,8 @@ void Hid::PermitVibration(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto can_vibrate{rp.Pop<bool>()};
+ // nnSDK saves this value as a float. Since it can only be 1.0f or 0.0f we simplify this value
+ // by converting it to a bool
Settings::values.vibration_enabled.SetValue(can_vibrate);
LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate);
@@ -1335,9 +1530,12 @@ void Hid::PermitVibration(Kernel::HLERequestContext& ctx) {
void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_HID, "called");
+ // nnSDK checks if a float is greater than zero. We return the bool we stored earlier
+ const auto is_enabled = Settings::values.vibration_enabled.GetValue();
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(Settings::values.vibration_enabled.GetValue());
+ rb.Push(is_enabled);
}
void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) {
@@ -1683,14 +1881,361 @@ void Hid::IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx) {
rb.Push(false);
}
+void Hid::GetPalmaConnectionHandle(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadIdType npad_id;
+ 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>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
+ parameters.npad_id, parameters.applet_resource_user_id);
+
+ Controller_Palma::PalmaConnectionHandle handle;
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.GetPalmaConnectionHandle(parameters.npad_id, handle);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.PushRaw(handle);
+}
+
+void Hid::InitializePalma(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.InitializePalma(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::AcquirePalmaOperationCompleteEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(controller.AcquirePalmaOperationCompleteEvent(connection_handle));
+}
+
+void Hid::GetPalmaOperationInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ Controller_Palma::PalmaOperationType operation_type;
+ Controller_Palma::PalmaOperationData data;
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.GetPalmaOperationInfo(connection_handle, operation_type, data);
+
+ if (result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ ctx.WriteBuffer(data);
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(static_cast<u64>(operation_type));
+}
+
+void Hid::PlayPalmaActivity(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+ const auto palma_activity{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, palma_activity={}",
+ connection_handle.npad_id, palma_activity);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.PlayPalmaActivity(connection_handle, palma_activity);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::SetPalmaFrModeType(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+ const auto fr_mode{rp.PopEnum<Controller_Palma::PalmaFrModeType>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, fr_mode={}",
+ connection_handle.npad_id, fr_mode);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.SetPalmaFrModeType(connection_handle, fr_mode);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::ReadPalmaStep(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.ReadPalmaStep(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::EnablePalmaStep(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool is_enabled;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ Controller_Palma::PalmaConnectionHandle connection_handle;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, is_enabled={}",
+ parameters.connection_handle.npad_id, parameters.is_enabled);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result =
+ controller.EnablePalmaStep(parameters.connection_handle, parameters.is_enabled);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::ResetPalmaStep(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.ResetPalmaStep(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::ReadPalmaApplicationSection(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::WritePalmaApplicationSection(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::ReadPalmaUniqueCode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .ReadPalmaUniqueCode(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SetPalmaUniqueCodeInvalid(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .SetPalmaUniqueCodeInvalid(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::WritePalmaActivityEntry(Kernel::HLERequestContext& ctx) {
+ LOG_CRITICAL(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+ const auto unknown{rp.Pop<u64>()};
+
+ const auto buffer = ctx.ReadBuffer();
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, unknown={}",
+ connection_handle.npad_id, unknown);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .WritePalmaRgbLedPatternEntry(connection_handle, unknown);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::WritePalmaWaveEntry(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+ const auto wave_set{rp.PopEnum<Controller_Palma::PalmaWaveSet>()};
+ const auto unknown{rp.Pop<u64>()};
+ const auto t_mem_size{rp.Pop<u64>()};
+ const auto t_mem_handle{ctx.GetCopyHandle(0)};
+ const auto size{rp.Pop<u64>()};
+
+ ASSERT_MSG(t_mem_size == 0x3000, "t_mem_size is not 0x3000 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() == 0x3000, "t_mem has incorrect size");
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, connection_handle={}, wave_set={}, unkown={}, "
+ "t_mem_handle=0x{:08X}, t_mem_size={}, size={}",
+ connection_handle.npad_id, wave_set, unknown, t_mem_handle, t_mem_size, size);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .WritePalmaWaveEntry(connection_handle, wave_set,
+ system.Memory().GetPointer(t_mem->GetSourceAddress()), t_mem_size);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ s32 database_id_version;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ Controller_Palma::PalmaConnectionHandle connection_handle;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, database_id_version={}",
+ parameters.connection_handle.npad_id, parameters.database_id_version);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .SetPalmaDataBaseIdentificationVersion(parameters.connection_handle,
+ parameters.database_id_version);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::GetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .GetPalmaDataBaseIdentificationVersion(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SuspendPalmaFeature(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::GetPalmaOperationResult(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ const auto result = applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .GetPalmaOperationResult(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::ReadPalmaPlayLog(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::ResetPalmaPlayLog(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto is_palma_all_connectable{rp.Pop<bool>()};
+ struct Parameters {
+ bool is_palma_all_connectable;
+ INSERT_PADDING_BYTES_NOINIT(7);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
LOG_WARNING(Service_HID,
- "(STUBBED) called, applet_resource_user_id={}, is_palma_all_connectable={}",
- applet_resource_user_id, is_palma_all_connectable);
+ "(STUBBED) called, is_palma_all_connectable={},applet_resource_user_id={}",
+ parameters.is_palma_all_connectable, parameters.applet_resource_user_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .SetIsPalmaAllConnectable(parameters.is_palma_all_connectable);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SetIsPalmaPairedConnectable(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::PairPalma(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .PairPalma(connection_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -1702,6 +2247,37 @@ void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called, palma_boost_mode={}", palma_boost_mode);
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .SetPalmaBoostMode(palma_boost_mode);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::CancelWritePalmaWaveEntry(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::EnablePalmaBoostMode(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::GetPalmaBluetoothAddress(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SetDisallowedPalmaConnection(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
@@ -1744,6 +2320,25 @@ void Hid::SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
+void Hid::IsFirmwareUpdateNeededForNotification(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ s32 unknown;
+ 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>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, unknown={}, applet_resource_user_id={}",
+ parameters.unknown, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(false);
+}
+
class HidDbg final : public ServiceFramework<HidDbg> {
public:
explicit HidDbg(Core::System& system_) : ServiceFramework{system_, "hid:dbg"} {
@@ -1932,12 +2527,18 @@ public:
{324, nullptr, "GetUniquePadButtonSet"},
{325, nullptr, "GetUniquePadColor"},
{326, nullptr, "GetUniquePadAppletDetailedUiType"},
+ {327, nullptr, "GetAbstractedPadIdDataFromNpad"},
+ {328, nullptr, "AttachAbstractedPadToNpad"},
+ {329, nullptr, "DetachAbstractedPadAll"},
+ {330, nullptr, "CheckAbstractedPadConnection"},
{500, nullptr, "SetAppletResourceUserId"},
{501, nullptr, "RegisterAppletResourceUserId"},
{502, nullptr, "UnregisterAppletResourceUserId"},
{503, nullptr, "EnableAppletToGetInput"},
{504, nullptr, "SetAruidValidForVibration"},
{505, nullptr, "EnableAppletToGetSixAxisSensor"},
+ {506, nullptr, "EnableAppletToGetPadInput"},
+ {507, nullptr, "EnableAppletToGetTouchScreen"},
{510, nullptr, "SetVibrationMasterVolume"},
{511, nullptr, "GetVibrationMasterVolume"},
{512, nullptr, "BeginPermitVibrationSession"},
@@ -2124,32 +2725,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);
@@ -2157,8 +2732,8 @@ void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system
std::make_shared<HidSys>(system)->InstallAsService(service_manager);
std::make_shared<HidTmp>(system)->InstallAsService(service_manager);
- std::make_shared<IRS>(system)->InstallAsService(service_manager);
- std::make_shared<IRS_SYS>(system)->InstallAsService(service_manager);
+ std::make_shared<Service::IRS::IRS>(system)->InstallAsService(service_manager);
+ std::make_shared<Service::IRS::IRS_SYS>(system)->InstallAsService(service_manager);
std::make_shared<XCD_SYS>(system)->InstallAsService(service_manager);
}
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index c281081a7..340d26fdc 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -34,6 +33,7 @@ enum class HidController : std::size_t {
NPad,
Gesture,
ConsoleSixAxisSensor,
+ Palma,
MaxControllers,
};
@@ -59,13 +59,14 @@ public:
private:
template <typename T>
- void MakeController(HidController controller) {
- controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>(system.HIDCore());
+ void MakeController(HidController controller, u8* shared_memory) {
+ controllers[static_cast<std::size_t>(controller)] =
+ std::make_unique<T>(system.HIDCore(), shared_memory);
}
template <typename T>
- void MakeControllerWithServiceContext(HidController controller) {
+ void MakeControllerWithServiceContext(HidController controller, u8* shared_memory) {
controllers[static_cast<std::size_t>(controller)] =
- std::make_unique<T>(system.HIDCore(), service_context);
+ std::make_unique<T>(system.HIDCore(), shared_memory, service_context);
}
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
@@ -103,6 +104,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);
@@ -112,6 +114,11 @@ private:
void ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx);
void IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void EnableSixAxisSensorUnalteredPassthrough(Kernel::HLERequestContext& ctx);
+ void IsSixAxisSensorUnalteredPassthroughEnabled(Kernel::HLERequestContext& ctx);
+ void LoadSixAxisSensorCalibrationParameter(Kernel::HLERequestContext& ctx);
+ void GetSixAxisSensorIcInformation(Kernel::HLERequestContext& ctx);
+ void ResetIsSixAxisSensorDeviceNewlyAssigned(Kernel::HLERequestContext& ctx);
void ActivateGesture(Kernel::HLERequestContext& ctx);
void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
@@ -160,11 +167,40 @@ private:
void FinalizeSevenSixAxisSensor(Kernel::HLERequestContext& ctx);
void ResetSevenSixAxisSensorTimestamp(Kernel::HLERequestContext& ctx);
void IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx);
+ void GetPalmaConnectionHandle(Kernel::HLERequestContext& ctx);
+ void InitializePalma(Kernel::HLERequestContext& ctx);
+ void AcquirePalmaOperationCompleteEvent(Kernel::HLERequestContext& ctx);
+ void GetPalmaOperationInfo(Kernel::HLERequestContext& ctx);
+ void PlayPalmaActivity(Kernel::HLERequestContext& ctx);
+ void SetPalmaFrModeType(Kernel::HLERequestContext& ctx);
+ void ReadPalmaStep(Kernel::HLERequestContext& ctx);
+ void EnablePalmaStep(Kernel::HLERequestContext& ctx);
+ void ResetPalmaStep(Kernel::HLERequestContext& ctx);
+ void ReadPalmaApplicationSection(Kernel::HLERequestContext& ctx);
+ void WritePalmaApplicationSection(Kernel::HLERequestContext& ctx);
+ void ReadPalmaUniqueCode(Kernel::HLERequestContext& ctx);
+ void SetPalmaUniqueCodeInvalid(Kernel::HLERequestContext& ctx);
+ void WritePalmaActivityEntry(Kernel::HLERequestContext& ctx);
+ void WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx);
+ void WritePalmaWaveEntry(Kernel::HLERequestContext& ctx);
+ void SetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx);
+ void GetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx);
+ void SuspendPalmaFeature(Kernel::HLERequestContext& ctx);
+ void GetPalmaOperationResult(Kernel::HLERequestContext& ctx);
+ void ReadPalmaPlayLog(Kernel::HLERequestContext& ctx);
+ void ResetPalmaPlayLog(Kernel::HLERequestContext& ctx);
void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
+ void SetIsPalmaPairedConnectable(Kernel::HLERequestContext& ctx);
+ void PairPalma(Kernel::HLERequestContext& ctx);
void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
+ void CancelWritePalmaWaveEntry(Kernel::HLERequestContext& ctx);
+ void EnablePalmaBoostMode(Kernel::HLERequestContext& ctx);
+ void GetPalmaBluetoothAddress(Kernel::HLERequestContext& ctx);
+ void SetDisallowedPalmaConnection(Kernel::HLERequestContext& ctx);
void SetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
void SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx);
+ void IsFirmwareUpdateNeededForNotification(Kernel::HLERequestContext& ctx);
std::shared_ptr<IAppletResource> applet_resource;
diff --git a/src/core/hle/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp
new file mode 100644
index 000000000..e5e50845f
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus.cpp
@@ -0,0 +1,524 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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, s64 time,
+ std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
+ const auto guard = LockService();
+ UpdateHidbus(user_data, ns_late);
+ return std::nullopt;
+ });
+
+ system_.CoreTiming().ScheduleLoopingEvent(hidbus_update_ns, 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) {
+ 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));
+ }
+ }
+}
+
+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..8c687f678
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus.h
@@ -0,0 +1,130 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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);
+ Result 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..b569b3c20
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
@@ -0,0 +1,71 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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..d3960f506
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/hidbus_base.h
@@ -0,0 +1,178 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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 {
+ Result 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..ad223d649
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/ringcon.cpp
@@ -0,0 +1,285 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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..b37df50ac
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/ringcon.h
@@ -0,0 +1,253 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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..dd439f60a
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/starlink.cpp
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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..0b1b7ba49
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/starlink.h
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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..e477443e3
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/stubbed.cpp
@@ -0,0 +1,51 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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..91165ceff
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/stubbed.h
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index 8812b8ecb..6a3453457 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -1,15 +1,28 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <random>
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_shared_memory.h"
+#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/service/hid/errors.h"
#include "core/hle/service/hid/irs.h"
+#include "core/hle/service/hid/irsensor/clustering_processor.h"
+#include "core/hle/service/hid/irsensor/image_transfer_processor.h"
+#include "core/hle/service/hid/irsensor/ir_led_processor.h"
+#include "core/hle/service/hid/irsensor/moment_processor.h"
+#include "core/hle/service/hid/irsensor/pointing_processor.h"
+#include "core/hle/service/hid/irsensor/tera_plugin_processor.h"
+#include "core/memory.h"
-namespace Service::HID {
+namespace Service::IRS {
IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} {
// clang-format off
@@ -35,25 +48,41 @@ IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} {
};
// clang-format on
+ u8* raw_shared_memory = system.Kernel().GetIrsSharedMem().GetPointer();
RegisterHandlers(functions);
+ shared_memory = std::construct_at(reinterpret_cast<StatusManager*>(raw_shared_memory));
+
+ npad_device = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
}
+IRS::~IRS() = default;
void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}",
+ applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}",
+ applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void IRS::GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_IRS, "called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_IRS, "called, applet_resource_user_id={}", applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
@@ -61,114 +90,462 @@ void IRS::GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
}
void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::IrSensor::IrCameraHandle camera_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>()};
+
+ LOG_WARNING(Service_IRS,
+ "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}",
+ parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
+ parameters.applet_resource_user_id);
+
+ auto result = IsIrCameraHandleValid(parameters.camera_handle);
+ if (result.IsSuccess()) {
+ // TODO: Stop Image processor
+ result = ResultSuccess;
+ }
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::IrSensor::IrCameraHandle camera_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ Core::IrSensor::PackedMomentProcessorConfig processor_config;
+ };
+ static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_IRS,
+ "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}",
+ parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
+ parameters.applet_resource_user_id);
+
+ const auto result = IsIrCameraHandleValid(parameters.camera_handle);
+
+ if (result.IsSuccess()) {
+ auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
+ MakeProcessor<MomentProcessor>(parameters.camera_handle, device);
+ auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle);
+ image_transfer_processor.SetConfig(parameters.processor_config);
+ }
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::IrSensor::IrCameraHandle camera_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ Core::IrSensor::PackedClusteringProcessorConfig processor_config;
+ };
+ static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_IRS,
+ "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}",
+ parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
+ parameters.applet_resource_user_id);
+
+ auto result = IsIrCameraHandleValid(parameters.camera_handle);
+
+ if (result.IsSuccess()) {
+ auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
+ MakeProcessorWithCoreContext<ClusteringProcessor>(parameters.camera_handle, device);
+ auto& image_transfer_processor =
+ GetProcessor<ClusteringProcessor>(parameters.camera_handle);
+ image_transfer_processor.SetConfig(parameters.processor_config);
+ }
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::IrSensor::IrCameraHandle camera_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ Core::IrSensor::PackedImageTransferProcessorConfig processor_config;
+ u32 transfer_memory_size;
+ };
+ static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+ const auto t_mem_handle{ctx.GetCopyHandle(0)};
+
+ auto t_mem =
+ system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
+
+ if (t_mem.IsNull()) {
+ LOG_ERROR(Service_IRS, "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() == parameters.transfer_memory_size, "t_mem has incorrect size");
+
+ u8* transfer_memory = system.Memory().GetPointer(t_mem->GetSourceAddress());
+
+ LOG_INFO(Service_IRS,
+ "called, npad_type={}, npad_id={}, transfer_memory_size={}, transfer_memory_size={}, "
+ "applet_resource_user_id={}",
+ parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
+ parameters.transfer_memory_size, t_mem->GetSize(), parameters.applet_resource_user_id);
+
+ const auto result = IsIrCameraHandleValid(parameters.camera_handle);
+
+ if (result.IsSuccess()) {
+ auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
+ MakeProcessorWithCoreContext<ImageTransferProcessor>(parameters.camera_handle, device);
+ auto& image_transfer_processor =
+ GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
+ image_transfer_processor.SetConfig(parameters.processor_config);
+ image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
+ }
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::IrSensor::IrCameraHandle camera_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>()};
+
+ LOG_DEBUG(Service_IRS, "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}",
+ parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
+ parameters.applet_resource_user_id);
+
+ const auto result = IsIrCameraHandleValid(parameters.camera_handle);
+ if (result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ const auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
+
+ if (device.mode != Core::IrSensor::IrSensorMode::ImageTransferProcessor) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidProcessorState);
+ return;
+ }
- IPC::ResponseBuilder rb{ctx, 5};
+ std::vector<u8> data{};
+ const auto& image_transfer_processor =
+ GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
+ const auto& state = image_transfer_processor.GetState(data);
+
+ ctx.WriteBuffer(data);
+ IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
- rb.PushRaw<u64>(system.CoreTiming().GetCPUTicks());
- rb.PushRaw<u32>(0);
+ rb.PushRaw(state);
}
void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::IrSensor::IrCameraHandle camera_handle;
+ Core::IrSensor::PackedTeraPluginProcessorConfig processor_config;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(
+ Service_IRS,
+ "(STUBBED) called, npad_type={}, npad_id={}, mode={}, mcu_version={}.{}, "
+ "applet_resource_user_id={}",
+ parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
+ parameters.processor_config.mode, parameters.processor_config.required_mcu_version.major,
+ parameters.processor_config.required_mcu_version.minor, parameters.applet_resource_user_id);
+
+ const auto result = IsIrCameraHandleValid(parameters.camera_handle);
+
+ if (result.IsSuccess()) {
+ auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
+ MakeProcessor<TeraPluginProcessor>(parameters.camera_handle, device);
+ auto& image_transfer_processor =
+ GetProcessor<TeraPluginProcessor>(parameters.camera_handle);
+ image_transfer_processor.SetConfig(parameters.processor_config);
+ }
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto npad_id{rp.PopEnum<Core::HID::NpadIdType>()};
+
+ if (npad_id > Core::HID::NpadIdType::Player8 && npad_id != Core::HID::NpadIdType::Invalid &&
+ npad_id != Core::HID::NpadIdType::Handheld) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(Service::HID::InvalidNpadId);
+ return;
+ }
+
+ Core::IrSensor::IrCameraHandle camera_handle{
+ .npad_id = static_cast<u8>(NpadIdTypeToIndex(npad_id)),
+ .npad_type = Core::HID::NpadStyleIndex::None,
+ };
+
+ LOG_INFO(Service_IRS, "called, npad_id={}, camera_npad_id={}, camera_npad_type={}", npad_id,
+ camera_handle.npad_id, camera_handle.npad_type);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.PushRaw<u32>(device_handle);
+ rb.PushRaw(camera_handle);
}
void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
+ const auto processor_config{rp.PopRaw<Core::IrSensor::PackedPointingProcessorConfig>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(
+ Service_IRS,
+ "(STUBBED) called, npad_type={}, npad_id={}, mcu_version={}.{}, applet_resource_user_id={}",
+ camera_handle.npad_type, camera_handle.npad_id, processor_config.required_mcu_version.major,
+ processor_config.required_mcu_version.minor, applet_resource_user_id);
+
+ auto result = IsIrCameraHandleValid(camera_handle);
+
+ if (result.IsSuccess()) {
+ auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle);
+ MakeProcessor<PointingProcessor>(camera_handle, device);
+ auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle);
+ image_transfer_processor.SetConfig(processor_config);
+ }
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::IrSensor::IrCameraHandle camera_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>()};
+
+ LOG_WARNING(Service_IRS,
+ "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}",
+ parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
+ parameters.applet_resource_user_id);
+
+ auto result = IsIrCameraHandleValid(parameters.camera_handle);
+ if (result.IsSuccess()) {
+ // TODO: Suspend image processor
+ result = ResultSuccess;
+ }
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
+ const auto mcu_version{rp.PopRaw<Core::IrSensor::PackedMcuVersion>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(
+ Service_IRS,
+ "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}, mcu_version={}.{}",
+ camera_handle.npad_type, camera_handle.npad_id, applet_resource_user_id, mcu_version.major,
+ mcu_version.minor);
+
+ auto result = IsIrCameraHandleValid(camera_handle);
+ if (result.IsSuccess()) {
+ // TODO: Check firmware version
+ result = ResultSuccess;
+ }
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IRS::SetFunctionLevel(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
+ const auto function_level{rp.PopRaw<Core::IrSensor::PackedFunctionLevel>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(
+ Service_IRS,
+ "(STUBBED) called, npad_type={}, npad_id={}, function_level={}, applet_resource_user_id={}",
+ camera_handle.npad_type, camera_handle.npad_id, function_level.function_level,
+ applet_resource_user_id);
+
+ auto result = IsIrCameraHandleValid(camera_handle);
+ if (result.IsSuccess()) {
+ // TODO: Set Function level
+ result = ResultSuccess;
+ }
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::IrSensor::IrCameraHandle camera_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ Core::IrSensor::PackedImageTransferProcessorExConfig processor_config;
+ u64 transfer_memory_size;
+ };
+ static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+ const auto t_mem_handle{ctx.GetCopyHandle(0)};
+
+ auto t_mem =
+ system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
+
+ u8* transfer_memory = system.Memory().GetPointer(t_mem->GetSourceAddress());
+
+ LOG_INFO(Service_IRS,
+ "called, npad_type={}, npad_id={}, transfer_memory_size={}, "
+ "applet_resource_user_id={}",
+ parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
+ parameters.transfer_memory_size, parameters.applet_resource_user_id);
+
+ auto result = IsIrCameraHandleValid(parameters.camera_handle);
+
+ if (result.IsSuccess()) {
+ auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
+ MakeProcessorWithCoreContext<ImageTransferProcessor>(parameters.camera_handle, device);
+ auto& image_transfer_processor =
+ GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
+ image_transfer_processor.SetConfig(parameters.processor_config);
+ image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
+ }
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
+ const auto processor_config{rp.PopRaw<Core::IrSensor::PackedIrLedProcessorConfig>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_IRS,
+ "(STUBBED) called, npad_type={}, npad_id={}, light_target={}, mcu_version={}.{} "
+ "applet_resource_user_id={}",
+ camera_handle.npad_type, camera_handle.npad_id, processor_config.light_target,
+ processor_config.required_mcu_version.major,
+ processor_config.required_mcu_version.minor, applet_resource_user_id);
+
+ auto result = IsIrCameraHandleValid(camera_handle);
+
+ if (result.IsSuccess()) {
+ auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle);
+ MakeProcessor<IrLedProcessor>(camera_handle, device);
+ auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle);
+ image_transfer_processor.SetConfig(processor_config);
+ }
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::IrSensor::IrCameraHandle camera_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>()};
+
+ LOG_WARNING(Service_IRS,
+ "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}",
+ parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
+ parameters.applet_resource_user_id);
+
+ auto result = IsIrCameraHandleValid(parameters.camera_handle);
+ if (result.IsSuccess()) {
+ // TODO: Stop image processor async
+ result = ResultSuccess;
+ }
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_IRS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::IrSensor::PackedFunctionLevel function_level;
+ 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>()};
+
+ LOG_WARNING(Service_IRS, "(STUBBED) called, function_level={}, applet_resource_user_id={}",
+ parameters.function_level.function_level, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
-IRS::~IRS() = default;
+Result IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const {
+ if (camera_handle.npad_id >
+ static_cast<u8>(NpadIdTypeToIndex(Core::HID::NpadIdType::Handheld))) {
+ return InvalidIrCameraHandle;
+ }
+ if (camera_handle.npad_type != Core::HID::NpadStyleIndex::None) {
+ return InvalidIrCameraHandle;
+ }
+ return ResultSuccess;
+}
+
+Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry(
+ const Core::IrSensor::IrCameraHandle& camera_handle) {
+ const auto npad_id_max_index = static_cast<u8>(sizeof(StatusManager::device));
+ ASSERT_MSG(camera_handle.npad_id < npad_id_max_index, "invalid npad_id");
+ return shared_memory->device[camera_handle.npad_id];
+}
IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} {
// clang-format off
@@ -185,4 +562,4 @@ IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} {
IRS_SYS::~IRS_SYS() = default;
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h
index 9bc6462b0..2e6115c73 100644
--- a/src/core/hle/service/hid/irs.h
+++ b/src/core/hle/service/hid/irs.h
@@ -1,16 +1,22 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include "core/hid/hid_types.h"
+#include "core/hid/irs_types.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
#include "core/hle/service/service.h"
namespace Core {
class System;
}
-namespace Service::HID {
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
+namespace Service::IRS {
class IRS final : public ServiceFramework<IRS> {
public:
@@ -18,6 +24,20 @@ public:
~IRS() override;
private:
+ // This is nn::irsensor::detail::AruidFormat
+ struct AruidFormat {
+ u64 sensor_aruid;
+ u64 sensor_aruid_status;
+ };
+ static_assert(sizeof(AruidFormat) == 0x10, "AruidFormat is an invalid size");
+
+ // This is nn::irsensor::detail::StatusManager
+ struct StatusManager {
+ std::array<Core::IrSensor::DeviceFormat, 9> device;
+ std::array<AruidFormat, 5> aruid;
+ };
+ static_assert(sizeof(StatusManager) == 0x8000, "StatusManager is an invalid size");
+
void ActivateIrsensor(Kernel::HLERequestContext& ctx);
void DeactivateIrsensor(Kernel::HLERequestContext& ctx);
void GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx);
@@ -37,7 +57,55 @@ private:
void StopImageProcessorAsync(Kernel::HLERequestContext& ctx);
void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx);
- const u32 device_handle{0xABCD};
+ Result IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const;
+ Core::IrSensor::DeviceFormat& GetIrCameraSharedMemoryDeviceEntry(
+ const Core::IrSensor::IrCameraHandle& camera_handle);
+
+ template <typename T>
+ void MakeProcessor(const Core::IrSensor::IrCameraHandle& handle,
+ Core::IrSensor::DeviceFormat& device_state) {
+ const auto index = static_cast<std::size_t>(handle.npad_id);
+ if (index > sizeof(processors)) {
+ LOG_CRITICAL(Service_IRS, "Invalid index {}", index);
+ return;
+ }
+ processors[index] = std::make_unique<T>(device_state);
+ }
+
+ template <typename T>
+ void MakeProcessorWithCoreContext(const Core::IrSensor::IrCameraHandle& handle,
+ Core::IrSensor::DeviceFormat& device_state) {
+ const auto index = static_cast<std::size_t>(handle.npad_id);
+ if (index > sizeof(processors)) {
+ LOG_CRITICAL(Service_IRS, "Invalid index {}", index);
+ return;
+ }
+ processors[index] = std::make_unique<T>(system.HIDCore(), device_state, index);
+ }
+
+ template <typename T>
+ T& GetProcessor(const Core::IrSensor::IrCameraHandle& handle) {
+ const auto index = static_cast<std::size_t>(handle.npad_id);
+ if (index > sizeof(processors)) {
+ LOG_CRITICAL(Service_IRS, "Invalid index {}", index);
+ return static_cast<T&>(*processors[0]);
+ }
+ return static_cast<T&>(*processors[index]);
+ }
+
+ template <typename T>
+ const T& GetProcessor(const Core::IrSensor::IrCameraHandle& handle) const {
+ const auto index = static_cast<std::size_t>(handle.npad_id);
+ if (index > sizeof(processors)) {
+ LOG_CRITICAL(Service_IRS, "Invalid index {}", index);
+ return static_cast<T&>(*processors[0]);
+ }
+ return static_cast<T&>(*processors[index]);
+ }
+
+ Core::HID::EmulatedController* npad_device = nullptr;
+ StatusManager* shared_memory = nullptr;
+ std::array<std::unique_ptr<ProcessorBase>, 9> processors{};
};
class IRS_SYS final : public ServiceFramework<IRS_SYS> {
@@ -46,4 +114,4 @@ public:
~IRS_SYS() override;
};
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irs_ring_lifo.h b/src/core/hle/service/hid/irs_ring_lifo.h
new file mode 100644
index 000000000..255d1d296
--- /dev/null
+++ b/src/core/hle/service/hid/irs_ring_lifo.h
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+
+namespace Service::IRS {
+
+template <typename State, std::size_t max_buffer_size>
+struct Lifo {
+ s64 sampling_number{};
+ s64 buffer_count{};
+ std::array<State, max_buffer_size> entries{};
+
+ const State& ReadCurrentEntry() const {
+ return entries[GetBufferTail()];
+ }
+
+ const State& ReadPreviousEntry() const {
+ return entries[GetPreviousEntryIndex()];
+ }
+
+ s64 GetBufferTail() const {
+ return sampling_number % max_buffer_size;
+ }
+
+ std::size_t GetPreviousEntryIndex() const {
+ return static_cast<size_t>((GetBufferTail() + max_buffer_size - 1) % max_buffer_size);
+ }
+
+ std::size_t GetNextEntryIndex() const {
+ return static_cast<size_t>((GetBufferTail() + 1) % max_buffer_size);
+ }
+
+ void WriteNextEntry(const State& new_state) {
+ if (buffer_count < static_cast<s64>(max_buffer_size)) {
+ buffer_count++;
+ }
+ sampling_number++;
+ entries[GetBufferTail()] = new_state;
+ }
+};
+
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.cpp b/src/core/hle/service/hid/irsensor/clustering_processor.cpp
new file mode 100644
index 000000000..e2f4ae876
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/clustering_processor.cpp
@@ -0,0 +1,265 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <queue>
+
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/service/hid/irsensor/clustering_processor.h"
+
+namespace Service::IRS {
+ClusteringProcessor::ClusteringProcessor(Core::HID::HIDCore& hid_core_,
+ Core::IrSensor::DeviceFormat& device_format,
+ std::size_t npad_index)
+ : device{device_format} {
+ npad_device = hid_core_.GetEmulatedControllerByIndex(npad_index);
+
+ device.mode = Core::IrSensor::IrSensorMode::ClusteringProcessor;
+ device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
+ device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
+ SetDefaultConfig();
+
+ shared_memory = std::construct_at(
+ reinterpret_cast<ClusteringSharedMemory*>(&device_format.state.processor_raw_data));
+
+ Core::HID::ControllerUpdateCallback engine_callback{
+ .on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); },
+ .is_npad_service = true,
+ };
+ callback_key = npad_device->SetCallback(engine_callback);
+}
+
+ClusteringProcessor::~ClusteringProcessor() {
+ npad_device->DeleteCallback(callback_key);
+};
+
+void ClusteringProcessor::StartProcessor() {
+ device.camera_status = Core::IrSensor::IrCameraStatus::Available;
+ device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready;
+}
+
+void ClusteringProcessor::SuspendProcessor() {}
+
+void ClusteringProcessor::StopProcessor() {}
+
+void ClusteringProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) {
+ if (type != Core::HID::ControllerTriggerType::IrSensor) {
+ return;
+ }
+
+ next_state = {};
+ const auto camera_data = npad_device->GetCamera();
+ auto filtered_image = camera_data.data;
+
+ RemoveLowIntensityData(filtered_image);
+
+ const auto window_start_x = static_cast<std::size_t>(current_config.window_of_interest.x);
+ const auto window_start_y = static_cast<std::size_t>(current_config.window_of_interest.y);
+ const auto window_end_x =
+ window_start_x + static_cast<std::size_t>(current_config.window_of_interest.width);
+ const auto window_end_y =
+ window_start_y + static_cast<std::size_t>(current_config.window_of_interest.height);
+
+ for (std::size_t y = window_start_y; y < window_end_y; y++) {
+ for (std::size_t x = window_start_x; x < window_end_x; x++) {
+ u8 pixel = GetPixel(filtered_image, x, y);
+ if (pixel == 0) {
+ continue;
+ }
+ const auto cluster = GetClusterProperties(filtered_image, x, y);
+ if (cluster.pixel_count > current_config.pixel_count_max) {
+ continue;
+ }
+ if (cluster.pixel_count < current_config.pixel_count_min) {
+ continue;
+ }
+ // Cluster object limit reached
+ if (next_state.object_count >= next_state.data.size()) {
+ continue;
+ }
+ next_state.data[next_state.object_count] = cluster;
+ next_state.object_count++;
+ }
+ }
+
+ next_state.sampling_number = camera_data.sample;
+ next_state.timestamp = next_state.timestamp + 131;
+ next_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
+ shared_memory->clustering_lifo.WriteNextEntry(next_state);
+
+ if (!IsProcessorActive()) {
+ StartProcessor();
+ }
+}
+
+void ClusteringProcessor::RemoveLowIntensityData(std::vector<u8>& data) {
+ for (u8& pixel : data) {
+ if (pixel < current_config.pixel_count_min) {
+ pixel = 0;
+ }
+ }
+}
+
+ClusteringProcessor::ClusteringData ClusteringProcessor::GetClusterProperties(std::vector<u8>& data,
+ std::size_t x,
+ std::size_t y) {
+ using DataPoint = Common::Point<std::size_t>;
+ std::queue<DataPoint> search_points{};
+ ClusteringData current_cluster = GetPixelProperties(data, x, y);
+ SetPixel(data, x, y, 0);
+ search_points.emplace<DataPoint>({x, y});
+
+ while (!search_points.empty()) {
+ const auto point = search_points.front();
+ search_points.pop();
+
+ // Avoid negative numbers
+ if (point.x == 0 || point.y == 0) {
+ continue;
+ }
+
+ std::array<DataPoint, 4> new_points{
+ DataPoint{point.x - 1, point.y},
+ {point.x, point.y - 1},
+ {point.x + 1, point.y},
+ {point.x, point.y + 1},
+ };
+
+ for (const auto new_point : new_points) {
+ if (new_point.x >= width) {
+ continue;
+ }
+ if (new_point.y >= height) {
+ continue;
+ }
+ if (GetPixel(data, new_point.x, new_point.y) < current_config.object_intensity_min) {
+ continue;
+ }
+ const ClusteringData cluster = GetPixelProperties(data, new_point.x, new_point.y);
+ current_cluster = MergeCluster(current_cluster, cluster);
+ SetPixel(data, new_point.x, new_point.y, 0);
+ search_points.emplace<DataPoint>({new_point.x, new_point.y});
+ }
+ }
+
+ return current_cluster;
+}
+
+ClusteringProcessor::ClusteringData ClusteringProcessor::GetPixelProperties(
+ const std::vector<u8>& data, std::size_t x, std::size_t y) const {
+ return {
+ .average_intensity = GetPixel(data, x, y) / 255.0f,
+ .centroid =
+ {
+ .x = static_cast<f32>(x),
+ .y = static_cast<f32>(y),
+
+ },
+ .pixel_count = 1,
+ .bound =
+ {
+ .x = static_cast<s16>(x),
+ .y = static_cast<s16>(y),
+ .width = 1,
+ .height = 1,
+ },
+ };
+}
+
+ClusteringProcessor::ClusteringData ClusteringProcessor::MergeCluster(
+ const ClusteringData a, const ClusteringData b) const {
+ const f32 a_pixel_count = static_cast<f32>(a.pixel_count);
+ const f32 b_pixel_count = static_cast<f32>(b.pixel_count);
+ const f32 pixel_count = a_pixel_count + b_pixel_count;
+ const f32 average_intensity =
+ (a.average_intensity * a_pixel_count + b.average_intensity * b_pixel_count) / pixel_count;
+ const Core::IrSensor::IrsCentroid centroid = {
+ .x = (a.centroid.x * a_pixel_count + b.centroid.x * b_pixel_count) / pixel_count,
+ .y = (a.centroid.y * a_pixel_count + b.centroid.y * b_pixel_count) / pixel_count,
+ };
+ s16 bound_start_x = a.bound.x < b.bound.x ? a.bound.x : b.bound.x;
+ s16 bound_start_y = a.bound.y < b.bound.y ? a.bound.y : b.bound.y;
+ s16 a_bound_end_x = a.bound.x + a.bound.width;
+ s16 a_bound_end_y = a.bound.y + a.bound.height;
+ s16 b_bound_end_x = b.bound.x + b.bound.width;
+ s16 b_bound_end_y = b.bound.y + b.bound.height;
+
+ const Core::IrSensor::IrsRect bound = {
+ .x = bound_start_x,
+ .y = bound_start_y,
+ .width = a_bound_end_x > b_bound_end_x ? static_cast<s16>(a_bound_end_x - bound_start_x)
+ : static_cast<s16>(b_bound_end_x - bound_start_x),
+ .height = a_bound_end_y > b_bound_end_y ? static_cast<s16>(a_bound_end_y - bound_start_y)
+ : static_cast<s16>(b_bound_end_y - bound_start_y),
+ };
+
+ return {
+ .average_intensity = average_intensity,
+ .centroid = centroid,
+ .pixel_count = static_cast<u32>(pixel_count),
+ .bound = bound,
+ };
+}
+
+u8 ClusteringProcessor::GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const {
+ if ((y * width) + x > data.size()) {
+ return 0;
+ }
+ return data[(y * width) + x];
+}
+
+void ClusteringProcessor::SetPixel(std::vector<u8>& data, std::size_t x, std::size_t y, u8 value) {
+ if ((y * width) + x > data.size()) {
+ return;
+ }
+ data[(y * width) + x] = value;
+}
+
+void ClusteringProcessor::SetDefaultConfig() {
+ using namespace std::literals::chrono_literals;
+ current_config.camera_config.exposure_time = std::chrono::microseconds(200ms).count();
+ current_config.camera_config.gain = 2;
+ current_config.camera_config.is_negative_used = false;
+ current_config.camera_config.light_target = Core::IrSensor::CameraLightTarget::BrightLeds;
+ current_config.window_of_interest = {
+ .x = 0,
+ .y = 0,
+ .width = width,
+ .height = height,
+ };
+ current_config.pixel_count_min = 3;
+ current_config.pixel_count_max = static_cast<u32>(GetDataSize(format));
+ current_config.is_external_light_filter_enabled = true;
+ current_config.object_intensity_min = 150;
+
+ npad_device->SetCameraFormat(format);
+}
+
+void ClusteringProcessor::SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config) {
+ current_config.camera_config.exposure_time = config.camera_config.exposure_time;
+ current_config.camera_config.gain = config.camera_config.gain;
+ current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
+ current_config.camera_config.light_target =
+ static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
+ current_config.window_of_interest = config.window_of_interest;
+ current_config.pixel_count_min = config.pixel_count_min;
+ current_config.pixel_count_max = config.pixel_count_max;
+ current_config.is_external_light_filter_enabled = config.is_external_light_filter_enabled;
+ current_config.object_intensity_min = config.object_intensity_min;
+
+ LOG_INFO(Service_IRS,
+ "Processor config, exposure_time={}, gain={}, is_negative_used={}, "
+ "light_target={}, window_of_interest=({}, {}, {}, {}), pixel_count_min={}, "
+ "pixel_count_max={}, is_external_light_filter_enabled={}, object_intensity_min={}",
+ current_config.camera_config.exposure_time, current_config.camera_config.gain,
+ current_config.camera_config.is_negative_used,
+ current_config.camera_config.light_target, current_config.window_of_interest.x,
+ current_config.window_of_interest.y, current_config.window_of_interest.width,
+ current_config.window_of_interest.height, current_config.pixel_count_min,
+ current_config.pixel_count_max, current_config.is_external_light_filter_enabled,
+ current_config.object_intensity_min);
+
+ npad_device->SetCameraFormat(format);
+}
+
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.h b/src/core/hle/service/hid/irsensor/clustering_processor.h
new file mode 100644
index 000000000..dc01a8ea7
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/clustering_processor.h
@@ -0,0 +1,110 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hid/irs_types.h"
+#include "core/hle/service/hid/irs_ring_lifo.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
+namespace Service::IRS {
+class ClusteringProcessor final : public ProcessorBase {
+public:
+ explicit ClusteringProcessor(Core::HID::HIDCore& hid_core_,
+ Core::IrSensor::DeviceFormat& device_format,
+ std::size_t npad_index);
+ ~ClusteringProcessor() override;
+
+ // Called when the processor is initialized
+ void StartProcessor() override;
+
+ // Called when the processor is suspended
+ void SuspendProcessor() override;
+
+ // Called when the processor is stopped
+ void StopProcessor() override;
+
+ // Sets config parameters of the camera
+ void SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config);
+
+private:
+ static constexpr auto format = Core::IrSensor::ImageTransferProcessorFormat::Size320x240;
+ static constexpr std::size_t width = 320;
+ static constexpr std::size_t height = 240;
+
+ // This is nn::irsensor::ClusteringProcessorConfig
+ struct ClusteringProcessorConfig {
+ Core::IrSensor::CameraConfig camera_config;
+ Core::IrSensor::IrsRect window_of_interest;
+ u32 pixel_count_min;
+ u32 pixel_count_max;
+ u32 object_intensity_min;
+ bool is_external_light_filter_enabled;
+ INSERT_PADDING_BYTES(3);
+ };
+ static_assert(sizeof(ClusteringProcessorConfig) == 0x30,
+ "ClusteringProcessorConfig is an invalid size");
+
+ // This is nn::irsensor::AdaptiveClusteringProcessorConfig
+ struct AdaptiveClusteringProcessorConfig {
+ Core::IrSensor::AdaptiveClusteringMode mode;
+ Core::IrSensor::AdaptiveClusteringTargetDistance target_distance;
+ };
+ static_assert(sizeof(AdaptiveClusteringProcessorConfig) == 0x8,
+ "AdaptiveClusteringProcessorConfig is an invalid size");
+
+ // This is nn::irsensor::ClusteringData
+ struct ClusteringData {
+ f32 average_intensity;
+ Core::IrSensor::IrsCentroid centroid;
+ u32 pixel_count;
+ Core::IrSensor::IrsRect bound;
+ };
+ static_assert(sizeof(ClusteringData) == 0x18, "ClusteringData is an invalid size");
+
+ // This is nn::irsensor::ClusteringProcessorState
+ struct ClusteringProcessorState {
+ s64 sampling_number;
+ u64 timestamp;
+ u8 object_count;
+ INSERT_PADDING_BYTES(3);
+ Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
+ std::array<ClusteringData, 0x10> data;
+ };
+ static_assert(sizeof(ClusteringProcessorState) == 0x198,
+ "ClusteringProcessorState is an invalid size");
+
+ struct ClusteringSharedMemory {
+ Service::IRS::Lifo<ClusteringProcessorState, 6> clustering_lifo;
+ static_assert(sizeof(clustering_lifo) == 0x9A0, "clustering_lifo is an invalid size");
+ INSERT_PADDING_WORDS(0x11F);
+ };
+ static_assert(sizeof(ClusteringSharedMemory) == 0xE20,
+ "ClusteringSharedMemory is an invalid size");
+
+ void OnControllerUpdate(Core::HID::ControllerTriggerType type);
+ void RemoveLowIntensityData(std::vector<u8>& data);
+ ClusteringData GetClusterProperties(std::vector<u8>& data, std::size_t x, std::size_t y);
+ ClusteringData GetPixelProperties(const std::vector<u8>& data, std::size_t x,
+ std::size_t y) const;
+ ClusteringData MergeCluster(const ClusteringData a, const ClusteringData b) const;
+ u8 GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const;
+ void SetPixel(std::vector<u8>& data, std::size_t x, std::size_t y, u8 value);
+
+ // Sets config parameters of the camera
+ void SetDefaultConfig();
+
+ ClusteringSharedMemory* shared_memory = nullptr;
+ ClusteringProcessorState next_state{};
+
+ ClusteringProcessorConfig current_config{};
+ Core::IrSensor::DeviceFormat& device;
+ Core::HID::EmulatedController* npad_device;
+ int callback_key{};
+};
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp
new file mode 100644
index 000000000..98f0c579d
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp
@@ -0,0 +1,150 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/service/hid/irsensor/image_transfer_processor.h"
+
+namespace Service::IRS {
+ImageTransferProcessor::ImageTransferProcessor(Core::HID::HIDCore& hid_core_,
+ Core::IrSensor::DeviceFormat& device_format,
+ std::size_t npad_index)
+ : device{device_format} {
+ npad_device = hid_core_.GetEmulatedControllerByIndex(npad_index);
+
+ Core::HID::ControllerUpdateCallback engine_callback{
+ .on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); },
+ .is_npad_service = true,
+ };
+ callback_key = npad_device->SetCallback(engine_callback);
+
+ device.mode = Core::IrSensor::IrSensorMode::ImageTransferProcessor;
+ device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
+ device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
+}
+
+ImageTransferProcessor::~ImageTransferProcessor() {
+ npad_device->DeleteCallback(callback_key);
+};
+
+void ImageTransferProcessor::StartProcessor() {
+ is_active = true;
+ device.camera_status = Core::IrSensor::IrCameraStatus::Available;
+ device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready;
+ processor_state.sampling_number = 0;
+ processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
+}
+
+void ImageTransferProcessor::SuspendProcessor() {}
+
+void ImageTransferProcessor::StopProcessor() {}
+
+void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) {
+ if (type != Core::HID::ControllerTriggerType::IrSensor) {
+ return;
+ }
+ if (!is_transfer_memory_set) {
+ return;
+ }
+
+ const auto camera_data = npad_device->GetCamera();
+
+ // This indicates how much ambient light is precent
+ processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
+ processor_state.sampling_number = camera_data.sample;
+
+ if (camera_data.format != current_config.origin_format) {
+ LOG_WARNING(Service_IRS, "Wrong Input format {} expected {}", camera_data.format,
+ current_config.origin_format);
+ memset(transfer_memory, 0, GetDataSize(current_config.trimming_format));
+ return;
+ }
+
+ if (current_config.origin_format > current_config.trimming_format) {
+ LOG_WARNING(Service_IRS, "Origin format {} is smaller than trimming format {}",
+ current_config.origin_format, current_config.trimming_format);
+ memset(transfer_memory, 0, GetDataSize(current_config.trimming_format));
+ return;
+ }
+
+ std::vector<u8> window_data{};
+ const auto origin_width = GetDataWidth(current_config.origin_format);
+ const auto origin_height = GetDataHeight(current_config.origin_format);
+ const auto trimming_width = GetDataWidth(current_config.trimming_format);
+ const auto trimming_height = GetDataHeight(current_config.trimming_format);
+ window_data.resize(GetDataSize(current_config.trimming_format));
+
+ if (trimming_width + current_config.trimming_start_x > origin_width ||
+ trimming_height + current_config.trimming_start_y > origin_height) {
+ LOG_WARNING(Service_IRS,
+ "Trimming area ({}, {}, {}, {}) is outside of origin area ({}, {})",
+ current_config.trimming_start_x, current_config.trimming_start_y,
+ trimming_width, trimming_height, origin_width, origin_height);
+ memset(transfer_memory, 0, GetDataSize(current_config.trimming_format));
+ return;
+ }
+
+ for (std::size_t y = 0; y < trimming_height; y++) {
+ for (std::size_t x = 0; x < trimming_width; x++) {
+ const std::size_t window_index = (y * trimming_width) + x;
+ const std::size_t origin_index =
+ ((y + current_config.trimming_start_y) * origin_width) + x +
+ current_config.trimming_start_x;
+ window_data[window_index] = camera_data.data[origin_index];
+ }
+ }
+
+ memcpy(transfer_memory, window_data.data(), GetDataSize(current_config.trimming_format));
+
+ if (!IsProcessorActive()) {
+ StartProcessor();
+ }
+}
+
+void ImageTransferProcessor::SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config) {
+ current_config.camera_config.exposure_time = config.camera_config.exposure_time;
+ current_config.camera_config.gain = config.camera_config.gain;
+ current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
+ current_config.camera_config.light_target =
+ static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
+ current_config.origin_format =
+ static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.format);
+ current_config.trimming_format =
+ static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.format);
+ current_config.trimming_start_x = 0;
+ current_config.trimming_start_y = 0;
+
+ npad_device->SetCameraFormat(current_config.origin_format);
+}
+
+void ImageTransferProcessor::SetConfig(
+ Core::IrSensor::PackedImageTransferProcessorExConfig config) {
+ current_config.camera_config.exposure_time = config.camera_config.exposure_time;
+ current_config.camera_config.gain = config.camera_config.gain;
+ current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
+ current_config.camera_config.light_target =
+ static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
+ current_config.origin_format =
+ static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.origin_format);
+ current_config.trimming_format =
+ static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.trimming_format);
+ current_config.trimming_start_x = config.trimming_start_x;
+ current_config.trimming_start_y = config.trimming_start_y;
+
+ npad_device->SetCameraFormat(current_config.origin_format);
+}
+
+void ImageTransferProcessor::SetTransferMemoryPointer(u8* t_mem) {
+ is_transfer_memory_set = true;
+ transfer_memory = t_mem;
+}
+
+Core::IrSensor::ImageTransferProcessorState ImageTransferProcessor::GetState(
+ std::vector<u8>& data) const {
+ const auto size = GetDataSize(current_config.trimming_format);
+ data.resize(size);
+ memcpy(data.data(), transfer_memory, size);
+ return processor_state;
+}
+
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.h b/src/core/hle/service/hid/irsensor/image_transfer_processor.h
new file mode 100644
index 000000000..393df492d
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.h
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hid/irs_types.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
+namespace Service::IRS {
+class ImageTransferProcessor final : public ProcessorBase {
+public:
+ explicit ImageTransferProcessor(Core::HID::HIDCore& hid_core_,
+ Core::IrSensor::DeviceFormat& device_format,
+ std::size_t npad_index);
+ ~ImageTransferProcessor() override;
+
+ // Called when the processor is initialized
+ void StartProcessor() override;
+
+ // Called when the processor is suspended
+ void SuspendProcessor() override;
+
+ // Called when the processor is stopped
+ void StopProcessor() override;
+
+ // Sets config parameters of the camera
+ void SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config);
+ void SetConfig(Core::IrSensor::PackedImageTransferProcessorExConfig config);
+
+ // Transfer memory where the image data will be stored
+ void SetTransferMemoryPointer(u8* t_mem);
+
+ Core::IrSensor::ImageTransferProcessorState GetState(std::vector<u8>& data) const;
+
+private:
+ // This is nn::irsensor::ImageTransferProcessorConfig
+ struct ImageTransferProcessorConfig {
+ Core::IrSensor::CameraConfig camera_config;
+ Core::IrSensor::ImageTransferProcessorFormat format;
+ };
+ static_assert(sizeof(ImageTransferProcessorConfig) == 0x20,
+ "ImageTransferProcessorConfig is an invalid size");
+
+ // This is nn::irsensor::ImageTransferProcessorExConfig
+ struct ImageTransferProcessorExConfig {
+ Core::IrSensor::CameraConfig camera_config;
+ Core::IrSensor::ImageTransferProcessorFormat origin_format;
+ Core::IrSensor::ImageTransferProcessorFormat trimming_format;
+ u16 trimming_start_x;
+ u16 trimming_start_y;
+ bool is_external_light_filter_enabled;
+ INSERT_PADDING_BYTES(3);
+ };
+ static_assert(sizeof(ImageTransferProcessorExConfig) == 0x28,
+ "ImageTransferProcessorExConfig is an invalid size");
+
+ void OnControllerUpdate(Core::HID::ControllerTriggerType type);
+
+ ImageTransferProcessorExConfig current_config{};
+ Core::IrSensor::ImageTransferProcessorState processor_state{};
+ Core::IrSensor::DeviceFormat& device;
+ Core::HID::EmulatedController* npad_device;
+ int callback_key{};
+
+ u8* transfer_memory = nullptr;
+ bool is_transfer_memory_set = false;
+};
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.cpp b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp
new file mode 100644
index 000000000..8e6dd99e4
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/hid/irsensor/ir_led_processor.h"
+
+namespace Service::IRS {
+IrLedProcessor::IrLedProcessor(Core::IrSensor::DeviceFormat& device_format)
+ : device(device_format) {
+ device.mode = Core::IrSensor::IrSensorMode::IrLedProcessor;
+ device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
+ device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
+}
+
+IrLedProcessor::~IrLedProcessor() = default;
+
+void IrLedProcessor::StartProcessor() {}
+
+void IrLedProcessor::SuspendProcessor() {}
+
+void IrLedProcessor::StopProcessor() {}
+
+void IrLedProcessor::SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config) {
+ current_config.light_target =
+ static_cast<Core::IrSensor::CameraLightTarget>(config.light_target);
+}
+
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.h b/src/core/hle/service/hid/irsensor/ir_led_processor.h
new file mode 100644
index 000000000..c3d8693c9
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/ir_led_processor.h
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "core/hid/irs_types.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Service::IRS {
+class IrLedProcessor final : public ProcessorBase {
+public:
+ explicit IrLedProcessor(Core::IrSensor::DeviceFormat& device_format);
+ ~IrLedProcessor() override;
+
+ // Called when the processor is initialized
+ void StartProcessor() override;
+
+ // Called when the processor is suspended
+ void SuspendProcessor() override;
+
+ // Called when the processor is stopped
+ void StopProcessor() override;
+
+ // Sets config parameters of the camera
+ void SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config);
+
+private:
+ // This is nn::irsensor::IrLedProcessorConfig
+ struct IrLedProcessorConfig {
+ Core::IrSensor::CameraLightTarget light_target;
+ };
+ static_assert(sizeof(IrLedProcessorConfig) == 0x4, "IrLedProcessorConfig is an invalid size");
+
+ struct IrLedProcessorState {
+ s64 sampling_number;
+ u64 timestamp;
+ std::array<u8, 0x8> data;
+ };
+ static_assert(sizeof(IrLedProcessorState) == 0x18, "IrLedProcessorState is an invalid size");
+
+ IrLedProcessorConfig current_config{};
+ Core::IrSensor::DeviceFormat& device;
+};
+
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/moment_processor.cpp b/src/core/hle/service/hid/irsensor/moment_processor.cpp
new file mode 100644
index 000000000..dbaca420a
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/moment_processor.cpp
@@ -0,0 +1,34 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/hid/irsensor/moment_processor.h"
+
+namespace Service::IRS {
+MomentProcessor::MomentProcessor(Core::IrSensor::DeviceFormat& device_format)
+ : device(device_format) {
+ device.mode = Core::IrSensor::IrSensorMode::MomentProcessor;
+ device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
+ device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
+}
+
+MomentProcessor::~MomentProcessor() = default;
+
+void MomentProcessor::StartProcessor() {}
+
+void MomentProcessor::SuspendProcessor() {}
+
+void MomentProcessor::StopProcessor() {}
+
+void MomentProcessor::SetConfig(Core::IrSensor::PackedMomentProcessorConfig config) {
+ current_config.camera_config.exposure_time = config.camera_config.exposure_time;
+ current_config.camera_config.gain = config.camera_config.gain;
+ current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
+ current_config.camera_config.light_target =
+ static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
+ current_config.window_of_interest = config.window_of_interest;
+ current_config.preprocess =
+ static_cast<Core::IrSensor::MomentProcessorPreprocess>(config.preprocess);
+ current_config.preprocess_intensity_threshold = config.preprocess_intensity_threshold;
+}
+
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/moment_processor.h b/src/core/hle/service/hid/irsensor/moment_processor.h
new file mode 100644
index 000000000..d4bd22e0f
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/moment_processor.h
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "core/hid/irs_types.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Service::IRS {
+class MomentProcessor final : public ProcessorBase {
+public:
+ explicit MomentProcessor(Core::IrSensor::DeviceFormat& device_format);
+ ~MomentProcessor() override;
+
+ // Called when the processor is initialized
+ void StartProcessor() override;
+
+ // Called when the processor is suspended
+ void SuspendProcessor() override;
+
+ // Called when the processor is stopped
+ void StopProcessor() override;
+
+ // Sets config parameters of the camera
+ void SetConfig(Core::IrSensor::PackedMomentProcessorConfig config);
+
+private:
+ // This is nn::irsensor::MomentProcessorConfig
+ struct MomentProcessorConfig {
+ Core::IrSensor::CameraConfig camera_config;
+ Core::IrSensor::IrsRect window_of_interest;
+ Core::IrSensor::MomentProcessorPreprocess preprocess;
+ u32 preprocess_intensity_threshold;
+ };
+ static_assert(sizeof(MomentProcessorConfig) == 0x28,
+ "MomentProcessorConfig is an invalid size");
+
+ // This is nn::irsensor::MomentStatistic
+ struct MomentStatistic {
+ f32 average_intensity;
+ Core::IrSensor::IrsCentroid centroid;
+ };
+ static_assert(sizeof(MomentStatistic) == 0xC, "MomentStatistic is an invalid size");
+
+ // This is nn::irsensor::MomentProcessorState
+ struct MomentProcessorState {
+ s64 sampling_number;
+ u64 timestamp;
+ Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
+ INSERT_PADDING_BYTES(4);
+ std::array<MomentStatistic, 0x30> stadistic;
+ };
+ static_assert(sizeof(MomentProcessorState) == 0x258, "MomentProcessorState is an invalid size");
+
+ MomentProcessorConfig current_config{};
+ Core::IrSensor::DeviceFormat& device;
+};
+
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.cpp b/src/core/hle/service/hid/irsensor/pointing_processor.cpp
new file mode 100644
index 000000000..929f177fc
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/pointing_processor.cpp
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/hid/irsensor/pointing_processor.h"
+
+namespace Service::IRS {
+PointingProcessor::PointingProcessor(Core::IrSensor::DeviceFormat& device_format)
+ : device(device_format) {
+ device.mode = Core::IrSensor::IrSensorMode::PointingProcessorMarker;
+ device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
+ device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
+}
+
+PointingProcessor::~PointingProcessor() = default;
+
+void PointingProcessor::StartProcessor() {}
+
+void PointingProcessor::SuspendProcessor() {}
+
+void PointingProcessor::StopProcessor() {}
+
+void PointingProcessor::SetConfig(Core::IrSensor::PackedPointingProcessorConfig config) {
+ current_config.window_of_interest = config.window_of_interest;
+}
+
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.h b/src/core/hle/service/hid/irsensor/pointing_processor.h
new file mode 100644
index 000000000..cf4930794
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/pointing_processor.h
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hid/irs_types.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Service::IRS {
+class PointingProcessor final : public ProcessorBase {
+public:
+ explicit PointingProcessor(Core::IrSensor::DeviceFormat& device_format);
+ ~PointingProcessor() override;
+
+ // Called when the processor is initialized
+ void StartProcessor() override;
+
+ // Called when the processor is suspended
+ void SuspendProcessor() override;
+
+ // Called when the processor is stopped
+ void StopProcessor() override;
+
+ // Sets config parameters of the camera
+ void SetConfig(Core::IrSensor::PackedPointingProcessorConfig config);
+
+private:
+ // This is nn::irsensor::PointingProcessorConfig
+ struct PointingProcessorConfig {
+ Core::IrSensor::IrsRect window_of_interest;
+ };
+ static_assert(sizeof(PointingProcessorConfig) == 0x8,
+ "PointingProcessorConfig is an invalid size");
+
+ struct PointingProcessorMarkerData {
+ u8 pointing_status;
+ INSERT_PADDING_BYTES(3);
+ u32 unknown;
+ float unkown_float1;
+ float position_x;
+ float position_y;
+ float unkown_float2;
+ Core::IrSensor::IrsRect window_of_interest;
+ };
+ static_assert(sizeof(PointingProcessorMarkerData) == 0x20,
+ "PointingProcessorMarkerData is an invalid size");
+
+ struct PointingProcessorMarkerState {
+ s64 sampling_number;
+ u64 timestamp;
+ std::array<PointingProcessorMarkerData, 0x3> data;
+ };
+ static_assert(sizeof(PointingProcessorMarkerState) == 0x70,
+ "PointingProcessorMarkerState is an invalid size");
+
+ PointingProcessorConfig current_config{};
+ Core::IrSensor::DeviceFormat& device;
+};
+
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/processor_base.cpp b/src/core/hle/service/hid/irsensor/processor_base.cpp
new file mode 100644
index 000000000..4d43ca17a
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/processor_base.cpp
@@ -0,0 +1,67 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Service::IRS {
+
+ProcessorBase::ProcessorBase() {}
+ProcessorBase::~ProcessorBase() = default;
+
+bool ProcessorBase::IsProcessorActive() const {
+ return is_active;
+}
+
+std::size_t ProcessorBase::GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const {
+ switch (format) {
+ case Core::IrSensor::ImageTransferProcessorFormat::Size320x240:
+ return 320 * 240;
+ case Core::IrSensor::ImageTransferProcessorFormat::Size160x120:
+ return 160 * 120;
+ case Core::IrSensor::ImageTransferProcessorFormat::Size80x60:
+ return 80 * 60;
+ case Core::IrSensor::ImageTransferProcessorFormat::Size40x30:
+ return 40 * 30;
+ case Core::IrSensor::ImageTransferProcessorFormat::Size20x15:
+ return 20 * 15;
+ default:
+ return 0;
+ }
+}
+
+std::size_t ProcessorBase::GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const {
+ switch (format) {
+ case Core::IrSensor::ImageTransferProcessorFormat::Size320x240:
+ return 320;
+ case Core::IrSensor::ImageTransferProcessorFormat::Size160x120:
+ return 160;
+ case Core::IrSensor::ImageTransferProcessorFormat::Size80x60:
+ return 80;
+ case Core::IrSensor::ImageTransferProcessorFormat::Size40x30:
+ return 40;
+ case Core::IrSensor::ImageTransferProcessorFormat::Size20x15:
+ return 20;
+ default:
+ return 0;
+ }
+}
+
+std::size_t ProcessorBase::GetDataHeight(
+ Core::IrSensor::ImageTransferProcessorFormat format) const {
+ switch (format) {
+ case Core::IrSensor::ImageTransferProcessorFormat::Size320x240:
+ return 240;
+ case Core::IrSensor::ImageTransferProcessorFormat::Size160x120:
+ return 120;
+ case Core::IrSensor::ImageTransferProcessorFormat::Size80x60:
+ return 60;
+ case Core::IrSensor::ImageTransferProcessorFormat::Size40x30:
+ return 30;
+ case Core::IrSensor::ImageTransferProcessorFormat::Size20x15:
+ return 15;
+ default:
+ return 0;
+ }
+}
+
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/processor_base.h b/src/core/hle/service/hid/irsensor/processor_base.h
new file mode 100644
index 000000000..bc0d2977b
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/processor_base.h
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hid/irs_types.h"
+
+namespace Service::IRS {
+class ProcessorBase {
+public:
+ explicit ProcessorBase();
+ virtual ~ProcessorBase();
+
+ virtual void StartProcessor() = 0;
+ virtual void SuspendProcessor() = 0;
+ virtual void StopProcessor() = 0;
+
+ bool IsProcessorActive() const;
+
+protected:
+ /// Returns the number of bytes the image uses
+ std::size_t GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const;
+
+ /// Returns the width of the image
+ std::size_t GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const;
+
+ /// Returns the height of the image
+ std::size_t GetDataHeight(Core::IrSensor::ImageTransferProcessorFormat format) const;
+
+ bool is_active{false};
+};
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp
new file mode 100644
index 000000000..e691c840a
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/hid/irsensor/tera_plugin_processor.h"
+
+namespace Service::IRS {
+TeraPluginProcessor::TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format)
+ : device(device_format) {
+ device.mode = Core::IrSensor::IrSensorMode::TeraPluginProcessor;
+ device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
+ device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
+}
+
+TeraPluginProcessor::~TeraPluginProcessor() = default;
+
+void TeraPluginProcessor::StartProcessor() {}
+
+void TeraPluginProcessor::SuspendProcessor() {}
+
+void TeraPluginProcessor::StopProcessor() {}
+
+void TeraPluginProcessor::SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config) {
+ current_config.mode = config.mode;
+ current_config.unknown_1 = config.unknown_1;
+ current_config.unknown_2 = config.unknown_2;
+ current_config.unknown_3 = config.unknown_3;
+}
+
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.h b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h
new file mode 100644
index 000000000..bbea7ed0b
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "core/hid/irs_types.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Service::IRS {
+class TeraPluginProcessor final : public ProcessorBase {
+public:
+ explicit TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format);
+ ~TeraPluginProcessor() override;
+
+ // Called when the processor is initialized
+ void StartProcessor() override;
+
+ // Called when the processor is suspended
+ void SuspendProcessor() override;
+
+ // Called when the processor is stopped
+ void StopProcessor() override;
+
+ // Sets config parameters of the camera
+ void SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config);
+
+private:
+ // This is nn::irsensor::TeraPluginProcessorConfig
+ struct TeraPluginProcessorConfig {
+ u8 mode;
+ u8 unknown_1;
+ u8 unknown_2;
+ u8 unknown_3;
+ };
+ static_assert(sizeof(TeraPluginProcessorConfig) == 0x4,
+ "TeraPluginProcessorConfig is an invalid size");
+
+ struct TeraPluginProcessorState {
+ s64 sampling_number;
+ u64 timestamp;
+ Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
+ std::array<u8, 0x12c> data;
+ };
+ static_assert(sizeof(TeraPluginProcessorState) == 0x140,
+ "TeraPluginProcessorState is an invalid size");
+
+ TeraPluginProcessorConfig current_config{};
+ Core::IrSensor::DeviceFormat& device;
+};
+
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/ring_lifo.h b/src/core/hle/service/hid/ring_lifo.h
index 44c20d967..65eb7ea02 100644
--- a/src/core/hle/service/hid/ring_lifo.h
+++ b/src/core/hle/service/hid/ring_lifo.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/hid/xcd.cpp b/src/core/hle/service/hid/xcd.cpp
index b1efa3d05..75cc266ea 100644
--- a/src/core/hle/service/hid/xcd.cpp
+++ b/src/core/hle/service/hid/xcd.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/hid/xcd.h"
diff --git a/src/core/hle/service/hid/xcd.h b/src/core/hle/service/hid/xcd.h
index 54932c228..fe0b91b91 100644
--- a/src/core/hle/service/hid/xcd.h
+++ b/src/core/hle/service/hid/xcd.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/jit/jit.cpp b/src/core/hle/service/jit/jit.cpp
new file mode 100644
index 000000000..8f2920c51
--- /dev/null
+++ b/src/core/hle/service/jit/jit.cpp
@@ -0,0 +1,404 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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_, Kernel::KProcess& process_, CodeRange user_rx,
+ CodeRange user_ro)
+ : ServiceFramework{system_, "IJitEnvironment", ServiceThreadType::CreateNew},
+ process{&process_}, 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) {
+ LOG_DEBUG(Service_JIT, "called");
+
+ struct InputParameters {
+ u32 data_size;
+ u64 command;
+ std::array<CodeRange, 2> ranges;
+ Struct32 data;
+ };
+
+ struct OutputParameters {
+ s32 return_value;
+ std::array<CodeRange, 2> ranges;
+ };
+
+ IPC::RequestParser rp{ctx};
+ const auto parameters{rp.PopRaw<InputParameters>()};
+
+ // Optional input/output buffers
+ std::vector<u8> input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::vector<u8>()};
+ std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0);
+
+ // Function call prototype:
+ // void GenerateCode(s32* ret, CodeRange* c0_out, CodeRange* c1_out, JITConfiguration* cfg,
+ // u64 cmd, u8* input_buf, size_t input_size, CodeRange* c0_in,
+ // CodeRange* c1_in, Struct32* data, size_t data_size, u8* output_buf,
+ // size_t output_size);
+ //
+ // The command argument is used to control the behavior of the plugin during code
+ // generation. The configuration allows the plugin to access the output code ranges, and the
+ // other arguments are used to transfer state between the game and the plugin.
+
+ const VAddr ret_ptr{context.AddHeap(0u)};
+ const VAddr c0_in_ptr{context.AddHeap(parameters.ranges[0])};
+ const VAddr c1_in_ptr{context.AddHeap(parameters.ranges[1])};
+ const VAddr c0_out_ptr{context.AddHeap(ClearSize(parameters.ranges[0]))};
+ const VAddr c1_out_ptr{context.AddHeap(ClearSize(parameters.ranges[1]))};
+
+ 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)};
+
+ // The callback does not directly return a value, it only writes to the output pointer
+ context.CallFunction(callbacks.GenerateCode, ret_ptr, c0_out_ptr, c1_out_ptr,
+ configuration_ptr, parameters.command, input_ptr, input_buffer.size(),
+ c0_in_ptr, c1_in_ptr, data_ptr, parameters.data_size, output_ptr,
+ output_buffer.size());
+
+ const s32 return_value{context.GetHeap<s32>(ret_ptr)};
+
+ if (return_value == 0) {
+ // The callback has written to the output executable code range,
+ // requiring an instruction cache invalidation
+ system.InvalidateCpuInstructionCacheRange(configuration.user_rx_memory.offset,
+ configuration.user_rx_memory.size);
+
+ // Write back to the IPC output buffer, if provided
+ if (ctx.CanWriteBuffer()) {
+ context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size());
+ ctx.WriteBuffer(output_buffer.data(), output_buffer.size());
+ }
+
+ const OutputParameters out{
+ .return_value = return_value,
+ .ranges =
+ {
+ context.GetHeap<CodeRange>(c0_out_ptr),
+ context.GetHeap<CodeRange>(c1_out_ptr),
+ },
+ };
+
+ IPC::ResponseBuilder rb{ctx, 8};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(out);
+ } else {
+ LOG_WARNING(Service_JIT, "plugin GenerateCode callback failed");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ }
+ };
+
+ void Control(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_JIT, "called");
+
+ IPC::RequestParser rp{ctx};
+ const auto command{rp.PopRaw<u64>()};
+
+ // Optional input/output buffers
+ std::vector<u8> input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::vector<u8>()};
+ std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0);
+
+ // Function call prototype:
+ // u64 Control(s32* ret, JITConfiguration* cfg, u64 cmd, u8* input_buf, size_t input_size,
+ // u8* output_buf, size_t output_size);
+ //
+ // This function is used to set up the state of the plugin before code generation, generally
+ // passing objects like pointers to VM state from the game. It is usually called once.
+
+ const VAddr ret_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, ret_ptr, configuration_ptr,
+ command, input_ptr, input_buffer.size(),
+ output_ptr, output_buffer.size())};
+
+ const s32 return_value{context.GetHeap<s32>(ret_ptr)};
+
+ if (wrapper_value == 0 && return_value == 0) {
+ // Write back to the IPC output buffer, if provided
+ 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) {
+ LOG_DEBUG(Service_JIT, "called");
+
+ IPC::RequestParser rp{ctx};
+ const auto tmem_size{rp.PopRaw<u64>()};
+ const auto tmem_handle{ctx.GetCopyHandle(0)};
+ const auto nro_plugin{ctx.ReadBuffer(1)};
+
+ 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;
+ }
+
+ auto tmem{process->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;
+ }
+
+ // Set up the configuration with the required TransferMemory address
+ configuration.transfer_memory.offset = tmem->GetSourceAddress();
+ configuration.transfer_memory.size = tmem_size;
+
+ // Gather up all the callbacks from the loaded plugin
+ auto symbols{Core::Symbols::GetSymbols(nro_plugin, true)};
+ const auto GetSymbol{[&](const std::string& name) { return symbols[name].first; }};
+
+ callbacks.rtld_fini = GetSymbol("_fini");
+ callbacks.rtld_init = GetSymbol("_init");
+ callbacks.Control = GetSymbol("nnjitpluginControl");
+ callbacks.ResolveBasicSymbols = GetSymbol("nnjitpluginResolveBasicSymbols");
+ callbacks.SetupDiagnostics = GetSymbol("nnjitpluginSetupDiagnostics");
+ callbacks.Configure = GetSymbol("nnjitpluginConfigure");
+ callbacks.GenerateCode = GetSymbol("nnjitpluginGenerateCode");
+ callbacks.GetVersion = GetSymbol("nnjitpluginGetVersion");
+ callbacks.OnPrepared = GetSymbol("nnjitpluginOnPrepared");
+ callbacks.Keeper = GetSymbol("nnjitpluginKeeper");
+
+ 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.transfer_memory.offset,
+ configuration.transfer_memory.size);
+
+ // Run ELF constructors, if needed
+ if (callbacks.rtld_init != 0) {
+ context.CallFunction(callbacks.rtld_init);
+ }
+
+ // Function prototype:
+ // u64 GetVersion();
+ 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;
+ }
+
+ // Function prototype:
+ // void ResolveBasicSymbols(void (*resolver)(const char* name));
+ const auto resolve{context.GetHelper("_resolve")};
+ if (callbacks.ResolveBasicSymbols != 0) {
+ context.CallFunction(callbacks.ResolveBasicSymbols, resolve);
+ }
+
+ // Function prototype:
+ // void SetupDiagnostics(u32 enabled, void (**resolver)(const char* name));
+ const auto resolve_ptr{context.AddHeap(resolve)};
+ if (callbacks.SetupDiagnostics != 0) {
+ context.CallFunction(callbacks.SetupDiagnostics, 0u, resolve_ptr);
+ }
+
+ // Function prototype:
+ // void Configure(u32* memory_flags);
+ context.CallFunction(callbacks.Configure, 0ull);
+
+ // Function prototype:
+ // void OnPrepared(JITConfiguration* cfg);
+ 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) {
+ LOG_DEBUG(Service_JIT, "called");
+
+ 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 transfer_memory;
+ CodeRange sys_rx_memory;
+ CodeRange sys_ro_memory;
+ };
+
+ static CodeRange ClearSize(CodeRange in) {
+ in.size = 0;
+ return in;
+ }
+
+ Kernel::KScopedAutoObject<Kernel::KProcess> process;
+ 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 process_handle{ctx.GetCopyHandle(0)};
+ const auto rx_mem_handle{ctx.GetCopyHandle(1)};
+ const auto ro_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;
+ }
+
+ // Fetch using the handle table for the current process here,
+ // since we are not multiprocess yet.
+ const auto& handle_table{system.CurrentProcess()->GetHandleTable()};
+
+ auto process{handle_table.GetObject<Kernel::KProcess>(process_handle)};
+ if (process.IsNull()) {
+ LOG_ERROR(Service_JIT, "process is null for handle=0x{:08X}", process_handle);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ auto rx_mem{handle_table.GetObject<Kernel::KCodeMemory>(rx_mem_handle)};
+ if (rx_mem.IsNull()) {
+ LOG_ERROR(Service_JIT, "rx_mem is null for handle=0x{:08X}", rx_mem_handle);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ auto ro_mem{handle_table.GetObject<Kernel::KCodeMemory>(ro_mem_handle)};
+ if (ro_mem.IsNull()) {
+ LOG_ERROR(Service_JIT, "ro_mem is null for handle=0x{:08X}", ro_mem_handle);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ const CodeRange user_rx{
+ .offset = rx_mem->GetSourceAddress(),
+ .size = parameters.rx_size,
+ };
+
+ const CodeRange user_ro{
+ .offset = ro_mem->GetSourceAddress(),
+ .size = parameters.ro_size,
+ };
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IJitEnvironment>(system, *process, 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..af0f5b4f3
--- /dev/null
+++ b/src/core/hle/service/jit/jit.h
@@ -0,0 +1,19 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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..4ed3f02e2
--- /dev/null
+++ b/src/core/hle/service/jit/jit_context.cpp
@@ -0,0 +1,426 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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/elf.h"
+#include "common/logging/log.h"
+#include "core/hle/service/jit/jit_context.h"
+#include "core/memory.h"
+
+using namespace Common::ELF;
+
+namespace Service::JIT {
+
+constexpr std::array<u8, 8> SVC0_ARM64 = {
+ 0x01, 0x00, 0x00, 0xd4, // svc #0
+ 0xc0, 0x03, 0x5f, 0xd6, // ret
+};
+
+constexpr std::array HELPER_FUNCTIONS{
+ "_stop", "_resolve", "_panic", "memcpy", "memmove", "memset",
+};
+
+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;
+ }
+
+ return false;
+ }
+
+ bool FixupRelocations() {
+ // The loaded NRO file has ELF relocations that must be processed before it can run.
+ // Normally this would be processed by RTLD, but in HLE context, we don't have
+ // the linker available, so we have to do it ourselves.
+
+ const VAddr mod_offset{callbacks->MemoryRead32(4)};
+ if (callbacks->MemoryRead32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) {
+ return false;
+ }
+
+ // For more info about dynamic entries, see the ELF ABI specification:
+ // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
+ // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
+ 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 == ElfDtRela) {
+ rela_dyn = dyn.d_un.d_ptr;
+ }
+ if (dyn.d_tag == ElfDtRelasz) {
+ num_rela = dyn.d_un.d_val / 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 (Elf64RelType(rela.r_info) != ElfAArch64Relative) {
+ 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 : HELPER_FUNCTIONS) {
+ helpers[name] = local_memory.size();
+ local_memory.insert(local_memory.end(), SVC0_ARM64.begin(), SVC0_ARM64.end());
+ }
+ }
+
+ void InsertStack() {
+ // Allocate enough space to avoid any reasonable risk of
+ // overflowing the stack during plugin execution
+ 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() {
+ // The first 8 integer registers are used for the first 8 integer
+ // arguments. Floating-point arguments are not handled at this time.
+ //
+ // If a function takes more than 8 arguments, then stack space is reserved
+ // for the remaining arguments, and the remaining arguments are inserted in
+ // ascending memory order, each argument aligned to an 8-byte boundary. The
+ // stack pointer must remain aligned to 16 bytes.
+ //
+ // For more info, see the AArch64 ABI PCS:
+ // https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst
+
+ 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);
+ }
+
+ // Reset the call state for the next invocation
+ 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) {
+ // Require all heap data types to have the same alignment as the
+ // stack pointer, for compatibility
+ const size_t num_bytes{Common::AlignUp(size, STACK_ALIGN)};
+
+ // Make additional memory space if required
+ 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) {
+ // Service calls are used to implement helper functionality.
+ //
+ // The most important of these is the _stop helper, which transfers control
+ // from the plugin back to HLE context to return a value. However, a few more
+ // are also implemented to reduce the need for direct ARM implementations of
+ // basic functionality, like memory operations.
+ //
+ // When we receive a helper request, the swi number will be zero, and the call
+ // will have originated from an address we know is a helper function. Otherwise,
+ // the plugin may be trying to issue a service call, which we shouldn't handle.
+
+ if (swi != 0) {
+ LOG_CRITICAL(Service_JIT, "plugin issued unknown service call {}", swi);
+ parent.jit->HaltExecution();
+ return;
+ }
+
+ u64 pc{parent.jit->GetPC() - 4};
+ auto& helpers{parent.helpers};
+
+ if (pc == helpers["memcpy"] || pc == helpers["memmove"]) {
+ const VAddr dest{parent.jit->GetRegister(0)};
+ const VAddr src{parent.jit->GetRegister(1)};
+ const size_t n{parent.jit->GetRegister(2)};
+
+ if (dest < src) {
+ for (size_t i = 0; i < n; i++) {
+ MemoryWrite8(dest + i, MemoryRead8(src + i));
+ }
+ } else {
+ for (size_t i = n; i > 0; i--) {
+ MemoryWrite8(dest + i - 1, MemoryRead8(src + i - 1));
+ }
+ }
+ } else if (pc == helpers["memset"]) {
+ const VAddr dest{parent.jit->GetRegister(0)};
+ const u64 c{parent.jit->GetRegister(1)};
+ const size_t n{parent.jit->GetRegister(2)};
+
+ for (size_t i = 0; i < n; i++) {
+ MemoryWrite8(dest + i, static_cast<u8>(c));
+ }
+ } else if (pc == helpers["_resolve"]) {
+ // X0 contains a char* for a symbol to resolve
+ const auto name{MemoryReadCString(parent.jit->GetRegister(0))};
+ const auto helper{helpers[name]};
+
+ if (helper != 0) {
+ parent.jit->SetRegister(0, helper);
+ } else {
+ LOG_WARNING(Service_JIT, "plugin requested unknown function {}", name);
+ parent.jit->SetRegister(0, helpers["_panic"]);
+ }
+ } else if (pc == helpers["_stop"]) {
+ parent.jit->HaltExecution();
+ } else if (pc == helpers["_panic"]) {
+ LOG_CRITICAL(Service_JIT, "plugin panicked!");
+ parent.jit->HaltExecution();
+ } else {
+ LOG_CRITICAL(Service_JIT, "plugin issued syscall at unknown address 0x{:x}", pc);
+ parent.jit->HaltExecution();
+ }
+}
+
+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..f17fc5e24
--- /dev/null
+++ b/src/core/hle/service/jit/jit_context.h
@@ -0,0 +1,65 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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>);
+ static_assert(!std::is_floating_point_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/kernel_helpers.cpp b/src/core/hle/service/kernel_helpers.cpp
index 62f4cdfb2..3e317367b 100644
--- a/src/core/hle/service/kernel_helpers.cpp
+++ b/src/core/hle/service/kernel_helpers.cpp
@@ -1,9 +1,10 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
+#include "core/core_timing.h"
#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_resource_limit.h"
@@ -15,9 +16,11 @@ namespace Service::KernelHelpers {
ServiceContext::ServiceContext(Core::System& system_, std::string name_)
: kernel(system_.Kernel()) {
+ // Create the process.
process = Kernel::KProcess::Create(kernel);
ASSERT(Kernel::KProcess::Initialize(process, system_, std::move(name_),
- Kernel::KProcess::ProcessType::Userland)
+ Kernel::KProcess::ProcessType::KernelInternal,
+ kernel.GetSystemResourceLimit())
.IsSuccess());
}
@@ -43,7 +46,7 @@ Kernel::KEvent* ServiceContext::CreateEvent(std::string&& name) {
}
// Initialize the event.
- event->Initialize(std::move(name));
+ event->Initialize(std::move(name), process);
// Commit the thread reservation.
event_reservation.Commit();
diff --git a/src/core/hle/service/kernel_helpers.h b/src/core/hle/service/kernel_helpers.h
index 4f3e95f67..6415838e5 100644
--- a/src/core/hle/service/kernel_helpers.h
+++ b/src/core/hle/service/kernel_helpers.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/lbl/lbl.cpp b/src/core/hle/service/lbl/lbl.cpp
index 5c8ae029c..c8415e0bf 100644
--- a/src/core/hle/service/lbl/lbl.cpp
+++ b/src/core/hle/service/lbl/lbl.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cmath>
#include <memory>
diff --git a/src/core/hle/service/lbl/lbl.h b/src/core/hle/service/lbl/lbl.h
index 9c2021026..6484105c2 100644
--- a/src/core/hle/service/lbl/lbl.h
+++ b/src/core/hle/service/lbl/lbl.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/ldn/errors.h b/src/core/hle/service/ldn/errors.h
deleted file mode 100644
index a718c5c66..000000000
--- a/src/core/hle/service/ldn/errors.h
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "core/hle/result.h"
-
-namespace Service::LDN {
-
-constexpr ResultCode ERROR_DISABLED{ErrorModule::LDN, 22};
-
-} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp
new file mode 100644
index 000000000..8f3c04550
--- /dev/null
+++ b/src/core/hle/service/ldn/lan_discovery.cpp
@@ -0,0 +1,633 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/ldn/lan_discovery.h"
+#include "core/internal_network/network.h"
+#include "core/internal_network/network_interface.h"
+
+namespace Service::LDN {
+
+LanStation::LanStation(s8 node_id_, LANDiscovery* discovery_)
+ : node_info(nullptr), status(NodeStatus::Disconnected), node_id(node_id_),
+ discovery(discovery_) {}
+
+LanStation::~LanStation() = default;
+
+NodeStatus LanStation::GetStatus() const {
+ return status;
+}
+
+void LanStation::OnClose() {
+ LOG_INFO(Service_LDN, "OnClose {}", node_id);
+ Reset();
+ discovery->UpdateNodes();
+}
+
+void LanStation::Reset() {
+ status = NodeStatus::Disconnected;
+};
+
+void LanStation::OverrideInfo() {
+ bool connected = GetStatus() == NodeStatus::Connected;
+ node_info->node_id = node_id;
+ node_info->is_connected = connected ? 1 : 0;
+}
+
+LANDiscovery::LANDiscovery(Network::RoomNetwork& room_network_)
+ : stations({{{1, this}, {2, this}, {3, this}, {4, this}, {5, this}, {6, this}, {7, this}}}),
+ room_network{room_network_} {}
+
+LANDiscovery::~LANDiscovery() {
+ if (inited) {
+ Result rc = Finalize();
+ LOG_INFO(Service_LDN, "Finalize: {}", rc.raw);
+ }
+}
+
+void LANDiscovery::InitNetworkInfo() {
+ network_info.common.bssid = GetFakeMac();
+ network_info.common.channel = WifiChannel::Wifi24_6;
+ network_info.common.link_level = LinkLevel::Good;
+ network_info.common.network_type = PackedNetworkType::Ldn;
+ network_info.common.ssid = fake_ssid;
+
+ auto& nodes = network_info.ldn.nodes;
+ for (std::size_t i = 0; i < NodeCountMax; i++) {
+ nodes[i].node_id = static_cast<s8>(i);
+ nodes[i].is_connected = 0;
+ }
+}
+
+void LANDiscovery::InitNodeStateChange() {
+ for (auto& node_update : node_changes) {
+ node_update.state_change = NodeStateChange::None;
+ }
+ for (auto& node_state : node_last_states) {
+ node_state = 0;
+ }
+}
+
+State LANDiscovery::GetState() const {
+ return state;
+}
+
+void LANDiscovery::SetState(State new_state) {
+ state = new_state;
+}
+
+Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network) const {
+ if (state == State::AccessPointCreated || state == State::StationConnected) {
+ std::memcpy(&out_network, &network_info, sizeof(network_info));
+ return ResultSuccess;
+ }
+
+ return ResultBadState;
+}
+
+Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network,
+ std::vector<NodeLatestUpdate>& out_updates,
+ std::size_t buffer_count) {
+ if (buffer_count > NodeCountMax) {
+ return ResultInvalidBufferCount;
+ }
+
+ if (state == State::AccessPointCreated || state == State::StationConnected) {
+ std::memcpy(&out_network, &network_info, sizeof(network_info));
+ for (std::size_t i = 0; i < buffer_count; i++) {
+ out_updates[i].state_change = node_changes[i].state_change;
+ node_changes[i].state_change = NodeStateChange::None;
+ }
+ return ResultSuccess;
+ }
+
+ return ResultBadState;
+}
+
+DisconnectReason LANDiscovery::GetDisconnectReason() const {
+ return disconnect_reason;
+}
+
+Result LANDiscovery::Scan(std::vector<NetworkInfo>& networks, u16& count,
+ const ScanFilter& filter) {
+ if (!IsFlagSet(filter.flag, ScanFilterFlag::NetworkType) ||
+ filter.network_type <= NetworkType::All) {
+ if (!IsFlagSet(filter.flag, ScanFilterFlag::Ssid) && filter.ssid.length >= SsidLengthMax) {
+ return ResultBadInput;
+ }
+ }
+
+ {
+ std::scoped_lock lock{packet_mutex};
+ scan_results.clear();
+
+ SendBroadcast(Network::LDNPacketType::Scan);
+ }
+
+ LOG_INFO(Service_LDN, "Waiting for scan replies");
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+
+ std::scoped_lock lock{packet_mutex};
+ for (const auto& [key, info] : scan_results) {
+ if (count >= networks.size()) {
+ break;
+ }
+
+ if (IsFlagSet(filter.flag, ScanFilterFlag::LocalCommunicationId)) {
+ if (filter.network_id.intent_id.local_communication_id !=
+ info.network_id.intent_id.local_communication_id) {
+ continue;
+ }
+ }
+ if (IsFlagSet(filter.flag, ScanFilterFlag::SessionId)) {
+ if (filter.network_id.session_id != info.network_id.session_id) {
+ continue;
+ }
+ }
+ if (IsFlagSet(filter.flag, ScanFilterFlag::NetworkType)) {
+ if (filter.network_type != static_cast<NetworkType>(info.common.network_type)) {
+ continue;
+ }
+ }
+ if (IsFlagSet(filter.flag, ScanFilterFlag::Ssid)) {
+ if (filter.ssid != info.common.ssid) {
+ continue;
+ }
+ }
+ if (IsFlagSet(filter.flag, ScanFilterFlag::SceneId)) {
+ if (filter.network_id.intent_id.scene_id != info.network_id.intent_id.scene_id) {
+ continue;
+ }
+ }
+
+ networks[count++] = info;
+ }
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::SetAdvertiseData(std::span<const u8> data) {
+ std::scoped_lock lock{packet_mutex};
+ const std::size_t size = data.size();
+ if (size > AdvertiseDataSizeMax) {
+ return ResultAdvertiseDataTooLarge;
+ }
+
+ std::memcpy(network_info.ldn.advertise_data.data(), data.data(), size);
+ network_info.ldn.advertise_data_size = static_cast<u16>(size);
+
+ UpdateNodes();
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::OpenAccessPoint() {
+ std::scoped_lock lock{packet_mutex};
+ disconnect_reason = DisconnectReason::None;
+ if (state == State::None) {
+ return ResultBadState;
+ }
+
+ ResetStations();
+ SetState(State::AccessPointOpened);
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::CloseAccessPoint() {
+ std::scoped_lock lock{packet_mutex};
+ if (state == State::None) {
+ return ResultBadState;
+ }
+
+ if (state == State::AccessPointCreated) {
+ DestroyNetwork();
+ }
+
+ ResetStations();
+ SetState(State::Initialized);
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::OpenStation() {
+ std::scoped_lock lock{packet_mutex};
+ disconnect_reason = DisconnectReason::None;
+ if (state == State::None) {
+ return ResultBadState;
+ }
+
+ ResetStations();
+ SetState(State::StationOpened);
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::CloseStation() {
+ std::scoped_lock lock{packet_mutex};
+ if (state == State::None) {
+ return ResultBadState;
+ }
+
+ if (state == State::StationConnected) {
+ Disconnect();
+ }
+
+ ResetStations();
+ SetState(State::Initialized);
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::CreateNetwork(const SecurityConfig& security_config,
+ const UserConfig& user_config,
+ const NetworkConfig& network_config) {
+ std::scoped_lock lock{packet_mutex};
+
+ if (state != State::AccessPointOpened) {
+ return ResultBadState;
+ }
+
+ InitNetworkInfo();
+ network_info.ldn.node_count_max = network_config.node_count_max;
+ network_info.ldn.security_mode = security_config.security_mode;
+
+ if (network_config.channel == WifiChannel::Default) {
+ network_info.common.channel = WifiChannel::Wifi24_6;
+ } else {
+ network_info.common.channel = network_config.channel;
+ }
+
+ std::independent_bits_engine<std::mt19937, 64, u64> bits_engine;
+ network_info.network_id.session_id.high = bits_engine();
+ network_info.network_id.session_id.low = bits_engine();
+ network_info.network_id.intent_id = network_config.intent_id;
+
+ NodeInfo& node0 = network_info.ldn.nodes[0];
+ const Result rc2 = GetNodeInfo(node0, user_config, network_config.local_communication_version);
+ if (rc2.IsError()) {
+ return ResultAccessPointConnectionFailed;
+ }
+
+ SetState(State::AccessPointCreated);
+
+ InitNodeStateChange();
+ node0.is_connected = 1;
+ UpdateNodes();
+
+ return rc2;
+}
+
+Result LANDiscovery::DestroyNetwork() {
+ for (auto local_ip : connected_clients) {
+ SendPacket(Network::LDNPacketType::DestroyNetwork, local_ip);
+ }
+
+ ResetStations();
+
+ SetState(State::AccessPointOpened);
+ lan_event();
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::Connect(const NetworkInfo& network_info_, const UserConfig& user_config,
+ u16 local_communication_version) {
+ std::scoped_lock lock{packet_mutex};
+ if (network_info_.ldn.node_count == 0) {
+ return ResultInvalidNodeCount;
+ }
+
+ Result rc = GetNodeInfo(node_info, user_config, local_communication_version);
+ if (rc.IsError()) {
+ return ResultConnectionFailed;
+ }
+
+ Ipv4Address node_host = network_info_.ldn.nodes[0].ipv4_address;
+ std::reverse(std::begin(node_host), std::end(node_host)); // htonl
+ host_ip = node_host;
+ SendPacket(Network::LDNPacketType::Connect, node_info, *host_ip);
+
+ InitNodeStateChange();
+
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::Disconnect() {
+ if (host_ip) {
+ SendPacket(Network::LDNPacketType::Disconnect, node_info, *host_ip);
+ }
+
+ SetState(State::StationOpened);
+ lan_event();
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::Initialize(LanEventFunc lan_event_, bool listening) {
+ std::scoped_lock lock{packet_mutex};
+ if (inited) {
+ return ResultSuccess;
+ }
+
+ for (auto& station : stations) {
+ station.discovery = this;
+ station.node_info = &network_info.ldn.nodes[station.node_id];
+ station.Reset();
+ }
+
+ connected_clients.clear();
+ lan_event = lan_event_;
+
+ SetState(State::Initialized);
+
+ inited = true;
+ return ResultSuccess;
+}
+
+Result LANDiscovery::Finalize() {
+ std::scoped_lock lock{packet_mutex};
+ Result rc = ResultSuccess;
+
+ if (inited) {
+ if (state == State::AccessPointCreated) {
+ DestroyNetwork();
+ }
+ if (state == State::StationConnected) {
+ Disconnect();
+ }
+
+ ResetStations();
+ inited = false;
+ }
+
+ SetState(State::None);
+
+ return rc;
+}
+
+void LANDiscovery::ResetStations() {
+ for (auto& station : stations) {
+ station.Reset();
+ }
+ connected_clients.clear();
+}
+
+void LANDiscovery::UpdateNodes() {
+ u8 count = 0;
+ for (auto& station : stations) {
+ bool connected = station.GetStatus() == NodeStatus::Connected;
+ if (connected) {
+ count++;
+ }
+ station.OverrideInfo();
+ }
+ network_info.ldn.node_count = count + 1;
+
+ for (auto local_ip : connected_clients) {
+ SendPacket(Network::LDNPacketType::SyncNetwork, network_info, local_ip);
+ }
+
+ OnNetworkInfoChanged();
+}
+
+void LANDiscovery::OnSyncNetwork(const NetworkInfo& info) {
+ network_info = info;
+ if (state == State::StationOpened) {
+ SetState(State::StationConnected);
+ }
+ OnNetworkInfoChanged();
+}
+
+void LANDiscovery::OnDisconnectFromHost() {
+ LOG_INFO(Service_LDN, "OnDisconnectFromHost state: {}", static_cast<int>(state));
+ host_ip = std::nullopt;
+ if (state == State::StationConnected) {
+ SetState(State::StationOpened);
+ lan_event();
+ }
+}
+
+void LANDiscovery::OnNetworkInfoChanged() {
+ if (IsNodeStateChanged()) {
+ lan_event();
+ }
+ return;
+}
+
+Network::IPv4Address LANDiscovery::GetLocalIp() const {
+ Network::IPv4Address local_ip{0xFF, 0xFF, 0xFF, 0xFF};
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ local_ip = room_member->GetFakeIpAddress();
+ }
+ }
+ return local_ip;
+}
+
+template <typename Data>
+void LANDiscovery::SendPacket(Network::LDNPacketType type, const Data& data,
+ Ipv4Address remote_ip) {
+ Network::LDNPacket packet;
+ packet.type = type;
+
+ packet.broadcast = false;
+ packet.local_ip = GetLocalIp();
+ packet.remote_ip = remote_ip;
+
+ packet.data.resize(sizeof(data));
+ std::memcpy(packet.data.data(), &data, sizeof(data));
+ SendPacket(packet);
+}
+
+void LANDiscovery::SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip) {
+ Network::LDNPacket packet;
+ packet.type = type;
+
+ packet.broadcast = false;
+ packet.local_ip = GetLocalIp();
+ packet.remote_ip = remote_ip;
+
+ SendPacket(packet);
+}
+
+template <typename Data>
+void LANDiscovery::SendBroadcast(Network::LDNPacketType type, const Data& data) {
+ Network::LDNPacket packet;
+ packet.type = type;
+
+ packet.broadcast = true;
+ packet.local_ip = GetLocalIp();
+
+ packet.data.resize(sizeof(data));
+ std::memcpy(packet.data.data(), &data, sizeof(data));
+ SendPacket(packet);
+}
+
+void LANDiscovery::SendBroadcast(Network::LDNPacketType type) {
+ Network::LDNPacket packet;
+ packet.type = type;
+
+ packet.broadcast = true;
+ packet.local_ip = GetLocalIp();
+
+ SendPacket(packet);
+}
+
+void LANDiscovery::SendPacket(const Network::LDNPacket& packet) {
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ room_member->SendLdnPacket(packet);
+ }
+ }
+}
+
+void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) {
+ std::scoped_lock lock{packet_mutex};
+ switch (packet.type) {
+ case Network::LDNPacketType::Scan: {
+ LOG_INFO(Frontend, "Scan packet received!");
+ if (state == State::AccessPointCreated) {
+ // Reply to the sender
+ SendPacket(Network::LDNPacketType::ScanResp, network_info, packet.local_ip);
+ }
+ break;
+ }
+ case Network::LDNPacketType::ScanResp: {
+ LOG_INFO(Frontend, "ScanResp packet received!");
+
+ NetworkInfo info{};
+ std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo));
+ scan_results.insert({info.common.bssid, info});
+
+ break;
+ }
+ case Network::LDNPacketType::Connect: {
+ LOG_INFO(Frontend, "Connect packet received!");
+
+ NodeInfo info{};
+ std::memcpy(&info, packet.data.data(), sizeof(NodeInfo));
+
+ connected_clients.push_back(packet.local_ip);
+
+ for (LanStation& station : stations) {
+ if (station.status != NodeStatus::Connected) {
+ *station.node_info = info;
+ station.status = NodeStatus::Connected;
+ break;
+ }
+ }
+
+ UpdateNodes();
+
+ break;
+ }
+ case Network::LDNPacketType::Disconnect: {
+ LOG_INFO(Frontend, "Disconnect packet received!");
+
+ connected_clients.erase(
+ std::remove(connected_clients.begin(), connected_clients.end(), packet.local_ip),
+ connected_clients.end());
+
+ NodeInfo info{};
+ std::memcpy(&info, packet.data.data(), sizeof(NodeInfo));
+
+ for (LanStation& station : stations) {
+ if (station.status == NodeStatus::Connected &&
+ station.node_info->mac_address == info.mac_address) {
+ station.OnClose();
+ break;
+ }
+ }
+
+ break;
+ }
+ case Network::LDNPacketType::DestroyNetwork: {
+ ResetStations();
+ OnDisconnectFromHost();
+ break;
+ }
+ case Network::LDNPacketType::SyncNetwork: {
+ if (state == State::StationOpened || state == State::StationConnected) {
+ LOG_INFO(Frontend, "SyncNetwork packet received!");
+ NetworkInfo info{};
+ std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo));
+
+ OnSyncNetwork(info);
+ } else {
+ LOG_INFO(Frontend, "SyncNetwork packet received but in wrong State!");
+ }
+
+ break;
+ }
+ default: {
+ LOG_INFO(Frontend, "ReceivePacket unhandled type {}", static_cast<int>(packet.type));
+ break;
+ }
+ }
+}
+
+bool LANDiscovery::IsNodeStateChanged() {
+ bool changed = false;
+ const auto& nodes = network_info.ldn.nodes;
+ for (int i = 0; i < NodeCountMax; i++) {
+ if (nodes[i].is_connected != node_last_states[i]) {
+ if (nodes[i].is_connected) {
+ node_changes[i].state_change |= NodeStateChange::Connect;
+ } else {
+ node_changes[i].state_change |= NodeStateChange::Disconnect;
+ }
+ node_last_states[i] = nodes[i].is_connected;
+ changed = true;
+ }
+ }
+ return changed;
+}
+
+bool LANDiscovery::IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const {
+ const auto flag_value = static_cast<u32>(flag);
+ const auto search_flag_value = static_cast<u32>(search_flag);
+ return (flag_value & search_flag_value) == search_flag_value;
+}
+
+int LANDiscovery::GetStationCount() const {
+ return static_cast<int>(
+ std::count_if(stations.begin(), stations.end(), [](const auto& station) {
+ return station.GetStatus() != NodeStatus::Disconnected;
+ }));
+}
+
+MacAddress LANDiscovery::GetFakeMac() const {
+ MacAddress mac{};
+ mac.raw[0] = 0x02;
+ mac.raw[1] = 0x00;
+
+ const auto ip = GetLocalIp();
+ memcpy(mac.raw.data() + 2, &ip, sizeof(ip));
+
+ return mac;
+}
+
+Result LANDiscovery::GetNodeInfo(NodeInfo& node, const UserConfig& userConfig,
+ u16 localCommunicationVersion) {
+ const auto network_interface = Network::GetSelectedNetworkInterface();
+
+ if (!network_interface) {
+ LOG_ERROR(Service_LDN, "No network interface available");
+ return ResultNoIpAddress;
+ }
+
+ node.mac_address = GetFakeMac();
+ node.is_connected = 1;
+ std::memcpy(node.user_name.data(), userConfig.user_name.data(), UserNameBytesMax + 1);
+ node.local_communication_version = localCommunicationVersion;
+
+ Ipv4Address current_address = GetLocalIp();
+ std::reverse(std::begin(current_address), std::end(current_address)); // ntohl
+ node.ipv4_address = current_address;
+
+ return ResultSuccess;
+}
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h
new file mode 100644
index 000000000..3833cd764
--- /dev/null
+++ b/src/core/hle/service/ldn/lan_discovery.h
@@ -0,0 +1,134 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <cstring>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <random>
+#include <span>
+#include <thread>
+#include <unordered_map>
+
+#include "common/logging/log.h"
+#include "common/socket_types.h"
+#include "core/hle/result.h"
+#include "core/hle/service/ldn/ldn_results.h"
+#include "core/hle/service/ldn/ldn_types.h"
+#include "network/network.h"
+
+namespace Service::LDN {
+
+class LANDiscovery;
+
+class LanStation {
+public:
+ LanStation(s8 node_id_, LANDiscovery* discovery_);
+ ~LanStation();
+
+ void OnClose();
+ NodeStatus GetStatus() const;
+ void Reset();
+ void OverrideInfo();
+
+protected:
+ friend class LANDiscovery;
+ NodeInfo* node_info;
+ NodeStatus status;
+ s8 node_id;
+ LANDiscovery* discovery;
+};
+
+class LANDiscovery {
+public:
+ using LanEventFunc = std::function<void()>;
+
+ LANDiscovery(Network::RoomNetwork& room_network_);
+ ~LANDiscovery();
+
+ State GetState() const;
+ void SetState(State new_state);
+
+ Result GetNetworkInfo(NetworkInfo& out_network) const;
+ Result GetNetworkInfo(NetworkInfo& out_network, std::vector<NodeLatestUpdate>& out_updates,
+ std::size_t buffer_count);
+
+ DisconnectReason GetDisconnectReason() const;
+ Result Scan(std::vector<NetworkInfo>& networks, u16& count, const ScanFilter& filter);
+ Result SetAdvertiseData(std::span<const u8> data);
+
+ Result OpenAccessPoint();
+ Result CloseAccessPoint();
+
+ Result OpenStation();
+ Result CloseStation();
+
+ Result CreateNetwork(const SecurityConfig& security_config, const UserConfig& user_config,
+ const NetworkConfig& network_config);
+ Result DestroyNetwork();
+
+ Result Connect(const NetworkInfo& network_info_, const UserConfig& user_config,
+ u16 local_communication_version);
+ Result Disconnect();
+
+ Result Initialize(LanEventFunc lan_event_ = empty_func, bool listening = true);
+ Result Finalize();
+
+ void ReceivePacket(const Network::LDNPacket& packet);
+
+protected:
+ friend class LanStation;
+
+ void InitNetworkInfo();
+ void InitNodeStateChange();
+
+ void ResetStations();
+ void UpdateNodes();
+
+ void OnSyncNetwork(const NetworkInfo& info);
+ void OnDisconnectFromHost();
+ void OnNetworkInfoChanged();
+
+ bool IsNodeStateChanged();
+ bool IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const;
+ int GetStationCount() const;
+ MacAddress GetFakeMac() const;
+ Result GetNodeInfo(NodeInfo& node, const UserConfig& user_config,
+ u16 local_communication_version);
+
+ Network::IPv4Address GetLocalIp() const;
+ template <typename Data>
+ void SendPacket(Network::LDNPacketType type, const Data& data, Ipv4Address remote_ip);
+ void SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip);
+ template <typename Data>
+ void SendBroadcast(Network::LDNPacketType type, const Data& data);
+ void SendBroadcast(Network::LDNPacketType type);
+ void SendPacket(const Network::LDNPacket& packet);
+
+ static const LanEventFunc empty_func;
+ static constexpr Ssid fake_ssid{"YuzuFakeSsidForLdn"};
+
+ bool inited{};
+ std::mutex packet_mutex;
+ std::array<LanStation, StationCountMax> stations;
+ std::array<NodeLatestUpdate, NodeCountMax> node_changes{};
+ std::array<u8, NodeCountMax> node_last_states{};
+ std::unordered_map<MacAddress, NetworkInfo, MACAddressHash> scan_results{};
+ NodeInfo node_info{};
+ NetworkInfo network_info{};
+ State state{State::None};
+ DisconnectReason disconnect_reason{DisconnectReason::None};
+
+ // TODO (flTobi): Should this be an std::set?
+ std::vector<Ipv4Address> connected_clients;
+ std::optional<Ipv4Address> host_ip;
+
+ LanEventFunc lan_event;
+
+ Network::RoomNetwork& room_network;
+};
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index 6ccbe9623..ea3e7e55a 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -1,14 +1,19 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
-#include "core/hle/ipc_helpers.h"
-#include "core/hle/result.h"
-#include "core/hle/service/ldn/errors.h"
+#include "core/core.h"
+#include "core/hle/service/ldn/lan_discovery.h"
#include "core/hle/service/ldn/ldn.h"
-#include "core/hle/service/sm/sm.h"
+#include "core/hle/service/ldn/ldn_results.h"
+#include "core/hle/service/ldn/ldn_types.h"
+#include "core/internal_network/network.h"
+#include "core/internal_network/network_interface.h"
+#include "network/network.h"
+
+// This is defined by synchapi.h and conflicts with ServiceContext::CreateEvent
+#undef CreateEvent
namespace Service::LDN {
@@ -101,74 +106,445 @@ class IUserLocalCommunicationService final
: public ServiceFramework<IUserLocalCommunicationService> {
public:
explicit IUserLocalCommunicationService(Core::System& system_)
- : ServiceFramework{system_, "IUserLocalCommunicationService"} {
+ : ServiceFramework{system_, "IUserLocalCommunicationService", ServiceThreadType::CreateNew},
+ service_context{system, "IUserLocalCommunicationService"},
+ room_network{system_.GetRoomNetwork()}, lan_discovery{room_network} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IUserLocalCommunicationService::GetState, "GetState"},
- {1, nullptr, "GetNetworkInfo"},
- {2, nullptr, "GetIpv4Address"},
- {3, nullptr, "GetDisconnectReason"},
- {4, nullptr, "GetSecurityParameter"},
- {5, nullptr, "GetNetworkConfig"},
- {100, nullptr, "AttachStateChangeEvent"},
- {101, nullptr, "GetNetworkInfoLatestUpdate"},
- {102, nullptr, "Scan"},
- {103, nullptr, "ScanPrivate"},
- {104, nullptr, "SetWirelessControllerRestriction"},
- {200, nullptr, "OpenAccessPoint"},
- {201, nullptr, "CloseAccessPoint"},
- {202, nullptr, "CreateNetwork"},
- {203, nullptr, "CreateNetworkPrivate"},
- {204, nullptr, "DestroyNetwork"},
+ {1, &IUserLocalCommunicationService::GetNetworkInfo, "GetNetworkInfo"},
+ {2, &IUserLocalCommunicationService::GetIpv4Address, "GetIpv4Address"},
+ {3, &IUserLocalCommunicationService::GetDisconnectReason, "GetDisconnectReason"},
+ {4, &IUserLocalCommunicationService::GetSecurityParameter, "GetSecurityParameter"},
+ {5, &IUserLocalCommunicationService::GetNetworkConfig, "GetNetworkConfig"},
+ {100, &IUserLocalCommunicationService::AttachStateChangeEvent, "AttachStateChangeEvent"},
+ {101, &IUserLocalCommunicationService::GetNetworkInfoLatestUpdate, "GetNetworkInfoLatestUpdate"},
+ {102, &IUserLocalCommunicationService::Scan, "Scan"},
+ {103, &IUserLocalCommunicationService::ScanPrivate, "ScanPrivate"},
+ {104, &IUserLocalCommunicationService::SetWirelessControllerRestriction, "SetWirelessControllerRestriction"},
+ {200, &IUserLocalCommunicationService::OpenAccessPoint, "OpenAccessPoint"},
+ {201, &IUserLocalCommunicationService::CloseAccessPoint, "CloseAccessPoint"},
+ {202, &IUserLocalCommunicationService::CreateNetwork, "CreateNetwork"},
+ {203, &IUserLocalCommunicationService::CreateNetworkPrivate, "CreateNetworkPrivate"},
+ {204, &IUserLocalCommunicationService::DestroyNetwork, "DestroyNetwork"},
{205, nullptr, "Reject"},
- {206, nullptr, "SetAdvertiseData"},
- {207, nullptr, "SetStationAcceptPolicy"},
- {208, nullptr, "AddAcceptFilterEntry"},
+ {206, &IUserLocalCommunicationService::SetAdvertiseData, "SetAdvertiseData"},
+ {207, &IUserLocalCommunicationService::SetStationAcceptPolicy, "SetStationAcceptPolicy"},
+ {208, &IUserLocalCommunicationService::AddAcceptFilterEntry, "AddAcceptFilterEntry"},
{209, nullptr, "ClearAcceptFilter"},
- {300, nullptr, "OpenStation"},
- {301, nullptr, "CloseStation"},
- {302, nullptr, "Connect"},
+ {300, &IUserLocalCommunicationService::OpenStation, "OpenStation"},
+ {301, &IUserLocalCommunicationService::CloseStation, "CloseStation"},
+ {302, &IUserLocalCommunicationService::Connect, "Connect"},
{303, nullptr, "ConnectPrivate"},
- {304, nullptr, "Disconnect"},
- {400, nullptr, "Initialize"},
- {401, nullptr, "Finalize"},
- {402, &IUserLocalCommunicationService::Initialize2, "Initialize2"}, // 7.0.0+
+ {304, &IUserLocalCommunicationService::Disconnect, "Disconnect"},
+ {400, &IUserLocalCommunicationService::Initialize, "Initialize"},
+ {401, &IUserLocalCommunicationService::Finalize, "Finalize"},
+ {402, &IUserLocalCommunicationService::Initialize2, "Initialize2"},
};
// clang-format on
RegisterHandlers(functions);
+
+ state_change_event =
+ service_context.CreateEvent("IUserLocalCommunicationService:StateChangeEvent");
+ }
+
+ ~IUserLocalCommunicationService() {
+ if (is_initialized) {
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ room_member->Unbind(ldn_packet_received);
+ }
+ }
+
+ service_context.CloseEvent(state_change_event);
+ }
+
+ /// Callback to parse and handle a received LDN packet.
+ void OnLDNPacketReceived(const Network::LDNPacket& packet) {
+ lan_discovery.ReceivePacket(packet);
+ }
+
+ void OnEventFired() {
+ state_change_event->GetWritableEvent().Signal();
}
void GetState(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ State state = State::Error;
+
+ if (is_initialized) {
+ state = lan_discovery.GetState();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(state);
+ }
+
+ void GetNetworkInfo(Kernel::HLERequestContext& ctx) {
+ const auto write_buffer_size = ctx.GetWriteBufferSize();
+
+ if (write_buffer_size != sizeof(NetworkInfo)) {
+ LOG_ERROR(Service_LDN, "Invalid buffer size {}", write_buffer_size);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultBadInput);
+ return;
+ }
+
+ NetworkInfo network_info{};
+ const auto rc = lan_discovery.GetNetworkInfo(network_info);
+ if (rc.IsError()) {
+ LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(rc);
+ return;
+ }
+
+ ctx.WriteBuffer<NetworkInfo>(network_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+ void GetIpv4Address(Kernel::HLERequestContext& ctx) {
+ const auto network_interface = Network::GetSelectedNetworkInterface();
+
+ if (!network_interface) {
+ LOG_ERROR(Service_LDN, "No network interface available");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultNoIpAddress);
+ return;
+ }
+
+ Ipv4Address current_address{Network::TranslateIPv4(network_interface->ip_address)};
+ Ipv4Address subnet_mask{Network::TranslateIPv4(network_interface->subnet_mask)};
+
+ // When we're connected to a room, spoof the hosts IP address
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ current_address = room_member->GetFakeIpAddress();
+ }
+ }
+
+ std::reverse(std::begin(current_address), std::end(current_address)); // ntohl
+ std::reverse(std::begin(subnet_mask), std::end(subnet_mask)); // ntohl
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(current_address);
+ rb.PushRaw(subnet_mask);
+ }
+
+ void GetDisconnectReason(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(lan_discovery.GetDisconnectReason());
+ }
- // Indicate a network error, as we do not actually emulate LDN
- rb.Push(static_cast<u32>(State::Error));
+ void GetSecurityParameter(Kernel::HLERequestContext& ctx) {
+ SecurityParameter security_parameter{};
+ NetworkInfo info{};
+ const Result rc = lan_discovery.GetNetworkInfo(info);
+
+ if (rc.IsError()) {
+ LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(rc);
+ return;
+ }
+
+ security_parameter.session_id = info.network_id.session_id;
+ std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(),
+ sizeof(SecurityParameter::data));
+
+ IPC::ResponseBuilder rb{ctx, 10};
+ rb.Push(rc);
+ rb.PushRaw<SecurityParameter>(security_parameter);
+ }
+ void GetNetworkConfig(Kernel::HLERequestContext& ctx) {
+ NetworkConfig config{};
+ NetworkInfo info{};
+ const Result rc = lan_discovery.GetNetworkInfo(info);
+
+ if (rc.IsError()) {
+ LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(rc);
+ return;
+ }
+
+ config.intent_id = info.network_id.intent_id;
+ config.channel = info.common.channel;
+ config.node_count_max = info.ldn.node_count_max;
+ config.local_communication_version = info.ldn.nodes[0].local_communication_version;
+
+ IPC::ResponseBuilder rb{ctx, 10};
+ rb.Push(rc);
+ rb.PushRaw<NetworkConfig>(config);
+ }
+
+ void AttachStateChangeEvent(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
+ rb.PushCopyObjects(state_change_event->GetReadableEvent());
}
- void Initialize2(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_LDN, "called");
+ void GetNetworkInfoLatestUpdate(Kernel::HLERequestContext& ctx) {
+ const std::size_t network_buffer_size = ctx.GetWriteBufferSize(0);
+ const std::size_t node_buffer_count = ctx.GetWriteBufferSize(1) / sizeof(NodeLatestUpdate);
- is_initialized = true;
+ if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) {
+ LOG_ERROR(Service_LDN, "Invalid buffer, size = {}, count = {}", network_buffer_size,
+ node_buffer_count);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultBadInput);
+ return;
+ }
+
+ NetworkInfo info{};
+ std::vector<NodeLatestUpdate> latest_update(node_buffer_count);
+
+ const auto rc = lan_discovery.GetNetworkInfo(info, latest_update, latest_update.size());
+ if (rc.IsError()) {
+ LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(rc);
+ return;
+ }
+
+ ctx.WriteBuffer(info, 0);
+ ctx.WriteBuffer(latest_update, 1);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_DISABLED);
+ rb.Push(ResultSuccess);
+ }
+
+ void Scan(Kernel::HLERequestContext& ctx) {
+ ScanImpl(ctx);
+ }
+
+ void ScanPrivate(Kernel::HLERequestContext& ctx) {
+ ScanImpl(ctx, true);
+ }
+
+ void ScanImpl(Kernel::HLERequestContext& ctx, bool is_private = false) {
+ IPC::RequestParser rp{ctx};
+ const auto channel{rp.PopEnum<WifiChannel>()};
+ const auto scan_filter{rp.PopRaw<ScanFilter>()};
+
+ const std::size_t network_info_size = ctx.GetWriteBufferSize() / sizeof(NetworkInfo);
+
+ if (network_info_size == 0) {
+ LOG_ERROR(Service_LDN, "Invalid buffer size {}", network_info_size);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultBadInput);
+ return;
+ }
+
+ u16 count = 0;
+ std::vector<NetworkInfo> network_infos(network_info_size);
+ Result rc = lan_discovery.Scan(network_infos, count, scan_filter);
+
+ LOG_INFO(Service_LDN,
+ "called, channel={}, filter_scan_flag={}, filter_network_type={}, is_private={}",
+ channel, scan_filter.flag, scan_filter.network_type, is_private);
+
+ ctx.WriteBuffer(network_infos);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(rc);
+ rb.Push<u32>(count);
+ }
+
+ void SetWirelessControllerRestriction(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
}
-private:
- enum class State {
- None,
- Initialized,
- AccessPointOpened,
- AccessPointCreated,
- StationOpened,
- StationConnected,
- Error,
- };
+ void OpenAccessPoint(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(lan_discovery.OpenAccessPoint());
+ }
+
+ void CloseAccessPoint(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(lan_discovery.CloseAccessPoint());
+ }
+
+ void CreateNetwork(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ CreateNetworkImpl(ctx);
+ }
+
+ void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ CreateNetworkImpl(ctx, true);
+ }
+
+ void CreateNetworkImpl(Kernel::HLERequestContext& ctx, bool is_private = false) {
+ IPC::RequestParser rp{ctx};
+
+ const auto security_config{rp.PopRaw<SecurityConfig>()};
+ [[maybe_unused]] const auto security_parameter{is_private ? rp.PopRaw<SecurityParameter>()
+ : SecurityParameter{}};
+ const auto user_config{rp.PopRaw<UserConfig>()};
+ rp.Pop<u32>(); // Padding
+ const auto network_Config{rp.PopRaw<NetworkConfig>()};
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(lan_discovery.CreateNetwork(security_config, user_config, network_Config));
+ }
+
+ void DestroyNetwork(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(lan_discovery.DestroyNetwork());
+ }
+
+ void SetAdvertiseData(Kernel::HLERequestContext& ctx) {
+ std::vector<u8> read_buffer = ctx.ReadBuffer();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(lan_discovery.SetAdvertiseData(read_buffer));
+ }
+
+ void SetStationAcceptPolicy(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void AddAcceptFilterEntry(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void OpenStation(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(lan_discovery.OpenStation());
+ }
+
+ void CloseStation(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(lan_discovery.CloseStation());
+ }
+
+ void Connect(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ SecurityConfig security_config;
+ UserConfig user_config;
+ u32 local_communication_version;
+ u32 option;
+ };
+ static_assert(sizeof(Parameters) == 0x7C, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_INFO(Service_LDN,
+ "called, passphrase_size={}, security_mode={}, "
+ "local_communication_version={}",
+ parameters.security_config.passphrase_size,
+ parameters.security_config.security_mode, parameters.local_communication_version);
+
+ const std::vector<u8> read_buffer = ctx.ReadBuffer();
+ if (read_buffer.size() != sizeof(NetworkInfo)) {
+ LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultBadInput);
+ return;
+ }
+
+ NetworkInfo network_info{};
+ std::memcpy(&network_info, read_buffer.data(), read_buffer.size());
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(lan_discovery.Connect(network_info, parameters.user_config,
+ static_cast<u16>(parameters.local_communication_version)));
+ }
+
+ void Disconnect(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(lan_discovery.Disconnect());
+ }
+
+ void Initialize(Kernel::HLERequestContext& ctx) {
+ const auto rc = InitializeImpl(ctx);
+ if (rc.IsError()) {
+ LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(rc);
+ }
+
+ void Finalize(Kernel::HLERequestContext& ctx) {
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ room_member->Unbind(ldn_packet_received);
+ }
+
+ is_initialized = false;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(lan_discovery.Finalize());
+ }
+
+ void Initialize2(Kernel::HLERequestContext& ctx) {
+ const auto rc = InitializeImpl(ctx);
+ if (rc.IsError()) {
+ LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(rc);
+ }
+
+ Result InitializeImpl(Kernel::HLERequestContext& ctx) {
+ const auto network_interface = Network::GetSelectedNetworkInterface();
+ if (!network_interface) {
+ LOG_ERROR(Service_LDN, "No network interface is set");
+ return ResultAirplaneModeEnabled;
+ }
+
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ ldn_packet_received = room_member->BindOnLdnPacketReceived(
+ [this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); });
+ } else {
+ LOG_ERROR(Service_LDN, "Couldn't bind callback!");
+ return ResultAirplaneModeEnabled;
+ }
+
+ lan_discovery.Initialize([&]() { OnEventFired(); });
+ is_initialized = true;
+ return ResultSuccess;
+ }
+
+ KernelHelpers::ServiceContext service_context;
+ Kernel::KEvent* state_change_event;
+ Network::RoomNetwork& room_network;
+ LANDiscovery lan_discovery;
+
+ // Callback identifier for the OnLDNPacketReceived event.
+ Network::RoomMember::CallbackHandle<Network::LDNPacket> ldn_packet_received;
bool is_initialized{};
};
@@ -274,7 +650,7 @@ public:
LOG_WARNING(Service_LDN, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_DISABLED);
+ rb.Push(ResultDisabled);
}
};
diff --git a/src/core/hle/service/ldn/ldn.h b/src/core/hle/service/ldn/ldn.h
index 3ccd9738b..6afe2ea6f 100644
--- a/src/core/hle/service/ldn/ldn.h
+++ b/src/core/hle/service/ldn/ldn.h
@@ -1,9 +1,14 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/result.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/sm/sm.h"
+
namespace Core {
class System;
}
diff --git a/src/core/hle/service/ldn/ldn_results.h b/src/core/hle/service/ldn/ldn_results.h
new file mode 100644
index 000000000..f340bda42
--- /dev/null
+++ b/src/core/hle/service/ldn/ldn_results.h
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::LDN {
+
+constexpr Result ResultAdvertiseDataTooLarge{ErrorModule::LDN, 10};
+constexpr Result ResultAuthenticationFailed{ErrorModule::LDN, 20};
+constexpr Result ResultDisabled{ErrorModule::LDN, 22};
+constexpr Result ResultAirplaneModeEnabled{ErrorModule::LDN, 23};
+constexpr Result ResultInvalidNodeCount{ErrorModule::LDN, 30};
+constexpr Result ResultConnectionFailed{ErrorModule::LDN, 31};
+constexpr Result ResultBadState{ErrorModule::LDN, 32};
+constexpr Result ResultNoIpAddress{ErrorModule::LDN, 33};
+constexpr Result ResultInvalidBufferCount{ErrorModule::LDN, 50};
+constexpr Result ResultAccessPointConnectionFailed{ErrorModule::LDN, 65};
+constexpr Result ResultAuthenticationTimeout{ErrorModule::LDN, 66};
+constexpr Result ResultMaximumNodeCount{ErrorModule::LDN, 67};
+constexpr Result ResultBadInput{ErrorModule::LDN, 96};
+constexpr Result ResultLocalCommunicationIdNotFound{ErrorModule::LDN, 97};
+constexpr Result ResultLocalCommunicationVersionTooLow{ErrorModule::LDN, 113};
+constexpr Result ResultLocalCommunicationVersionTooHigh{ErrorModule::LDN, 114};
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h
new file mode 100644
index 000000000..44c2c773b
--- /dev/null
+++ b/src/core/hle/service/ldn/ldn_types.h
@@ -0,0 +1,306 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <fmt/format.h>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "network/network.h"
+
+namespace Service::LDN {
+
+constexpr size_t SsidLengthMax = 32;
+constexpr size_t AdvertiseDataSizeMax = 384;
+constexpr size_t UserNameBytesMax = 32;
+constexpr int NodeCountMax = 8;
+constexpr int StationCountMax = NodeCountMax - 1;
+constexpr size_t PassphraseLengthMax = 64;
+
+enum class SecurityMode : u16 {
+ All,
+ Retail,
+ Debug,
+};
+
+enum class NodeStateChange : u8 {
+ None,
+ Connect,
+ Disconnect,
+ DisconnectAndConnect,
+};
+
+DECLARE_ENUM_FLAG_OPERATORS(NodeStateChange)
+
+enum class ScanFilterFlag : u32 {
+ None = 0,
+ LocalCommunicationId = 1 << 0,
+ SessionId = 1 << 1,
+ NetworkType = 1 << 2,
+ Ssid = 1 << 4,
+ SceneId = 1 << 5,
+ IntentId = LocalCommunicationId | SceneId,
+ NetworkId = IntentId | SessionId,
+};
+
+enum class NetworkType : u32 {
+ None,
+ General,
+ Ldn,
+ All,
+};
+
+enum class PackedNetworkType : u8 {
+ None,
+ General,
+ Ldn,
+ All,
+};
+
+enum class State : u32 {
+ None,
+ Initialized,
+ AccessPointOpened,
+ AccessPointCreated,
+ StationOpened,
+ StationConnected,
+ Error,
+};
+
+enum class DisconnectReason : s16 {
+ Unknown = -1,
+ None,
+ DisconnectedByUser,
+ DisconnectedBySystem,
+ DestroyedByUser,
+ DestroyedBySystem,
+ Rejected,
+ SignalLost,
+};
+
+enum class NetworkError {
+ Unknown = -1,
+ None = 0,
+ PortUnreachable,
+ TooManyPlayers,
+ VersionTooLow,
+ VersionTooHigh,
+ ConnectFailure,
+ ConnectNotFound,
+ ConnectTimeout,
+ ConnectRejected,
+ RejectFailed,
+};
+
+enum class AcceptPolicy : u8 {
+ AcceptAll,
+ RejectAll,
+ BlackList,
+ WhiteList,
+};
+
+enum class WifiChannel : s16 {
+ Default = 0,
+ Wifi24_1 = 1,
+ Wifi24_6 = 6,
+ Wifi24_11 = 11,
+ Wifi50_36 = 36,
+ Wifi50_40 = 40,
+ Wifi50_44 = 44,
+ Wifi50_48 = 48,
+};
+
+enum class LinkLevel : s8 {
+ Bad,
+ Low,
+ Good,
+ Excellent,
+};
+
+enum class NodeStatus : u8 {
+ Disconnected,
+ Connected,
+};
+
+struct NodeLatestUpdate {
+ NodeStateChange state_change;
+ INSERT_PADDING_BYTES(0x7); // Unknown
+};
+static_assert(sizeof(NodeLatestUpdate) == 0x8, "NodeLatestUpdate is an invalid size");
+
+struct SessionId {
+ u64 high;
+ u64 low;
+
+ bool operator==(const SessionId&) const = default;
+};
+static_assert(sizeof(SessionId) == 0x10, "SessionId is an invalid size");
+
+struct IntentId {
+ u64 local_communication_id;
+ INSERT_PADDING_BYTES(0x2); // Reserved
+ u16 scene_id;
+ INSERT_PADDING_BYTES(0x4); // Reserved
+};
+static_assert(sizeof(IntentId) == 0x10, "IntentId is an invalid size");
+
+struct NetworkId {
+ IntentId intent_id;
+ SessionId session_id;
+};
+static_assert(sizeof(NetworkId) == 0x20, "NetworkId is an invalid size");
+
+struct Ssid {
+ u8 length{};
+ std::array<char, SsidLengthMax + 1> raw{};
+
+ Ssid() = default;
+
+ constexpr explicit Ssid(std::string_view data) {
+ length = static_cast<u8>(std::min(data.size(), SsidLengthMax));
+ data.copy(raw.data(), length);
+ raw[length] = 0;
+ }
+
+ std::string GetStringValue() const {
+ return std::string(raw.data());
+ }
+
+ bool operator==(const Ssid& b) const {
+ return (length == b.length) && (std::memcmp(raw.data(), b.raw.data(), length) == 0);
+ }
+
+ bool operator!=(const Ssid& b) const {
+ return !operator==(b);
+ }
+};
+static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size");
+
+using Ipv4Address = std::array<u8, 4>;
+static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size");
+
+struct MacAddress {
+ std::array<u8, 6> raw{};
+
+ friend bool operator==(const MacAddress& lhs, const MacAddress& rhs) = default;
+};
+static_assert(sizeof(MacAddress) == 0x6, "MacAddress is an invalid size");
+
+struct MACAddressHash {
+ size_t operator()(const MacAddress& address) const {
+ u64 value{};
+ std::memcpy(&value, address.raw.data(), sizeof(address.raw));
+ return value;
+ }
+};
+
+struct ScanFilter {
+ NetworkId network_id;
+ NetworkType network_type;
+ MacAddress mac_address;
+ Ssid ssid;
+ INSERT_PADDING_BYTES(0x10);
+ ScanFilterFlag flag;
+};
+static_assert(sizeof(ScanFilter) == 0x60, "ScanFilter is an invalid size");
+
+struct CommonNetworkInfo {
+ MacAddress bssid;
+ Ssid ssid;
+ WifiChannel channel;
+ LinkLevel link_level;
+ PackedNetworkType network_type;
+ INSERT_PADDING_BYTES(0x4);
+};
+static_assert(sizeof(CommonNetworkInfo) == 0x30, "CommonNetworkInfo is an invalid size");
+
+struct NodeInfo {
+ Ipv4Address ipv4_address;
+ MacAddress mac_address;
+ s8 node_id;
+ u8 is_connected;
+ std::array<u8, UserNameBytesMax + 1> user_name;
+ INSERT_PADDING_BYTES(0x1); // Reserved
+ s16 local_communication_version;
+ INSERT_PADDING_BYTES(0x10); // Reserved
+};
+static_assert(sizeof(NodeInfo) == 0x40, "NodeInfo is an invalid size");
+
+struct LdnNetworkInfo {
+ std::array<u8, 0x10> security_parameter;
+ SecurityMode security_mode;
+ AcceptPolicy station_accept_policy;
+ u8 has_action_frame;
+ INSERT_PADDING_BYTES(0x2); // Padding
+ u8 node_count_max;
+ u8 node_count;
+ std::array<NodeInfo, NodeCountMax> nodes;
+ INSERT_PADDING_BYTES(0x2); // Reserved
+ u16 advertise_data_size;
+ std::array<u8, AdvertiseDataSizeMax> advertise_data;
+ INSERT_PADDING_BYTES(0x8C); // Reserved
+ u64 random_authentication_id;
+};
+static_assert(sizeof(LdnNetworkInfo) == 0x430, "LdnNetworkInfo is an invalid size");
+
+struct NetworkInfo {
+ NetworkId network_id;
+ CommonNetworkInfo common;
+ LdnNetworkInfo ldn;
+};
+static_assert(sizeof(NetworkInfo) == 0x480, "NetworkInfo is an invalid size");
+
+struct SecurityConfig {
+ SecurityMode security_mode;
+ u16 passphrase_size;
+ std::array<u8, PassphraseLengthMax> passphrase;
+};
+static_assert(sizeof(SecurityConfig) == 0x44, "SecurityConfig is an invalid size");
+
+struct UserConfig {
+ std::array<u8, UserNameBytesMax + 1> user_name;
+ INSERT_PADDING_BYTES(0xF); // Reserved
+};
+static_assert(sizeof(UserConfig) == 0x30, "UserConfig is an invalid size");
+
+#pragma pack(push, 4)
+struct ConnectRequest {
+ SecurityConfig security_config;
+ UserConfig user_config;
+ u32 local_communication_version;
+ u32 option_unknown;
+ NetworkInfo network_info;
+};
+static_assert(sizeof(ConnectRequest) == 0x4FC, "ConnectRequest is an invalid size");
+#pragma pack(pop)
+
+struct SecurityParameter {
+ std::array<u8, 0x10> data; // Data, used with the same key derivation as SecurityConfig
+ SessionId session_id;
+};
+static_assert(sizeof(SecurityParameter) == 0x20, "SecurityParameter is an invalid size");
+
+struct NetworkConfig {
+ IntentId intent_id;
+ WifiChannel channel;
+ u8 node_count_max;
+ INSERT_PADDING_BYTES(0x1); // Reserved
+ u16 local_communication_version;
+ INSERT_PADDING_BYTES(0xA); // Reserved
+};
+static_assert(sizeof(NetworkConfig) == 0x20, "NetworkConfig is an invalid size");
+
+struct AddressEntry {
+ Ipv4Address ipv4_address;
+ MacAddress mac_address;
+ INSERT_PADDING_BYTES(0x2); // Reserved
+};
+static_assert(sizeof(AddressEntry) == 0xC, "AddressEntry is an invalid size");
+
+struct AddressList {
+ std::array<AddressEntry, 0x8> addresses;
+};
+static_assert(sizeof(AddressList) == 0x60, "AddressList is an invalid size");
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index 9fc7bb1b1..becd6d1b9 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include <fmt/format.h>
@@ -12,7 +11,6 @@
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_page_table.h"
-#include "core/hle/kernel/k_system_control.h"
#include "core/hle/kernel/svc_results.h"
#include "core/hle/kernel/svc_types.h"
#include "core/hle/service/ldr/ldr.h"
@@ -22,20 +20,20 @@
namespace Service::LDR {
-constexpr ResultCode ERROR_INSUFFICIENT_ADDRESS_SPACE{ErrorModule::RO, 2};
-
-[[maybe_unused]] constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51};
-constexpr ResultCode ERROR_INVALID_NRO{ErrorModule::Loader, 52};
-constexpr ResultCode ERROR_INVALID_NRR{ErrorModule::Loader, 53};
-constexpr ResultCode ERROR_MISSING_NRR_HASH{ErrorModule::Loader, 54};
-constexpr ResultCode ERROR_MAXIMUM_NRO{ErrorModule::Loader, 55};
-constexpr ResultCode ERROR_MAXIMUM_NRR{ErrorModule::Loader, 56};
-constexpr ResultCode ERROR_ALREADY_LOADED{ErrorModule::Loader, 57};
-constexpr ResultCode ERROR_INVALID_ALIGNMENT{ErrorModule::Loader, 81};
-constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::Loader, 82};
-constexpr ResultCode ERROR_INVALID_NRO_ADDRESS{ErrorModule::Loader, 84};
-[[maybe_unused]] constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85};
-constexpr ResultCode ERROR_NOT_INITIALIZED{ErrorModule::Loader, 87};
+constexpr Result ERROR_INSUFFICIENT_ADDRESS_SPACE{ErrorModule::RO, 2};
+
+[[maybe_unused]] constexpr Result ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51};
+constexpr Result ERROR_INVALID_NRO{ErrorModule::Loader, 52};
+constexpr Result ERROR_INVALID_NRR{ErrorModule::Loader, 53};
+constexpr Result ERROR_MISSING_NRR_HASH{ErrorModule::Loader, 54};
+constexpr Result ERROR_MAXIMUM_NRO{ErrorModule::Loader, 55};
+constexpr Result ERROR_MAXIMUM_NRR{ErrorModule::Loader, 56};
+constexpr Result ERROR_ALREADY_LOADED{ErrorModule::Loader, 57};
+constexpr Result ERROR_INVALID_ALIGNMENT{ErrorModule::Loader, 81};
+constexpr Result ERROR_INVALID_SIZE{ErrorModule::Loader, 82};
+constexpr Result ERROR_INVALID_NRO_ADDRESS{ErrorModule::Loader, 84};
+[[maybe_unused]] constexpr Result ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85};
+constexpr Result ERROR_NOT_INITIALIZED{ErrorModule::Loader, 87};
constexpr std::size_t MAXIMUM_LOADED_RO{0x40};
constexpr std::size_t MAXIMUM_MAP_RETRIES{0x200};
@@ -161,7 +159,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"},
@@ -288,7 +287,7 @@ public:
}
bool ValidateRegionForMap(Kernel::KPageTable& page_table, VAddr start, std::size_t size) const {
- constexpr std::size_t padding_size{4 * Kernel::PageSize};
+ const std::size_t padding_size{page_table.GetNumGuardPages() * Kernel::PageSize};
const auto start_info{page_table.QueryInfo(start - 1)};
if (start_info.state != Kernel::KMemoryState::Free) {
@@ -308,31 +307,69 @@ public:
return (start + size + padding_size) <= (end_info.GetAddress() + end_info.GetSize());
}
- VAddr GetRandomMapRegion(const Kernel::KPageTable& page_table, std::size_t size) const {
- VAddr addr{};
- const std::size_t end_pages{(page_table.GetAliasCodeRegionSize() - size) >>
- Kernel::PageBits};
- do {
- addr = page_table.GetAliasCodeRegionStart() +
- (Kernel::KSystemControl::GenerateRandomRange(0, end_pages) << Kernel::PageBits);
- } while (!page_table.IsInsideAddressSpace(addr, size) ||
- page_table.IsInsideHeapRegion(addr, size) ||
- page_table.IsInsideAliasRegion(addr, size));
- return addr;
+ Result GetAvailableMapRegion(Kernel::KPageTable& page_table, u64 size, VAddr& out_addr) {
+ size = Common::AlignUp(size, Kernel::PageSize);
+ size += page_table.GetNumGuardPages() * Kernel::PageSize * 4;
+
+ const auto is_region_available = [&](VAddr addr) {
+ const auto end_addr = addr + size;
+ while (addr < end_addr) {
+ if (system.Memory().IsValidVirtualAddress(addr)) {
+ return false;
+ }
+
+ if (!page_table.IsInsideAddressSpace(out_addr, size)) {
+ return false;
+ }
+
+ if (page_table.IsInsideHeapRegion(out_addr, size)) {
+ return false;
+ }
+
+ if (page_table.IsInsideAliasRegion(out_addr, size)) {
+ return false;
+ }
+
+ addr += Kernel::PageSize;
+ }
+ return true;
+ };
+
+ bool succeeded = false;
+ const auto map_region_end =
+ page_table.GetAliasCodeRegionStart() + page_table.GetAliasCodeRegionSize();
+ while (current_map_addr < map_region_end) {
+ if (is_region_available(current_map_addr)) {
+ succeeded = true;
+ break;
+ }
+ current_map_addr += 0x100000;
+ }
+
+ if (!succeeded) {
+ ASSERT_MSG(false, "Out of address space!");
+ return Kernel::ResultOutOfMemory;
+ }
+
+ out_addr = current_map_addr;
+ current_map_addr += size;
+
+ return ResultSuccess;
}
- ResultVal<VAddr> MapProcessCodeMemory(Kernel::KProcess* process, VAddr baseAddress,
- u64 size) const {
+ ResultVal<VAddr> MapProcessCodeMemory(Kernel::KProcess* process, VAddr base_addr, u64 size) {
+ auto& page_table{process->PageTable()};
+ VAddr addr{};
+
for (std::size_t retry = 0; retry < MAXIMUM_MAP_RETRIES; retry++) {
- auto& page_table{process->PageTable()};
- const VAddr addr{GetRandomMapRegion(page_table, size)};
- const ResultCode result{page_table.MapCodeMemory(addr, baseAddress, size)};
+ R_TRY(GetAvailableMapRegion(page_table, size, addr));
+ const Result result{page_table.MapCodeMemory(addr, base_addr, size)};
if (result == Kernel::ResultInvalidCurrentMemory) {
continue;
}
- CASCADE_CODE(result);
+ R_TRY(result);
if (ValidateRegionForMap(page_table, addr, size)) {
return addr;
@@ -343,7 +380,7 @@ public:
}
ResultVal<VAddr> MapNro(Kernel::KProcess* process, VAddr nro_addr, std::size_t nro_size,
- VAddr bss_addr, std::size_t bss_size, std::size_t size) const {
+ VAddr bss_addr, std::size_t bss_size, std::size_t size) {
for (std::size_t retry = 0; retry < MAXIMUM_MAP_RETRIES; retry++) {
auto& page_table{process->PageTable()};
VAddr addr{};
@@ -352,12 +389,15 @@ 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{
- page_table.MapCodeMemory(addr + nro_size, bss_addr, bss_size)};
+ const Result result{page_table.MapCodeMemory(addr + nro_size, bss_addr, bss_size)};
if (result == Kernel::ResultInvalidCurrentMemory) {
continue;
@@ -378,8 +418,8 @@ public:
return ERROR_INSUFFICIENT_ADDRESS_SPACE;
}
- ResultCode LoadNro(Kernel::KProcess* process, const NROHeader& nro_header, VAddr nro_addr,
- VAddr start) const {
+ Result LoadNro(Kernel::KProcess* process, const NROHeader& nro_header, VAddr nro_addr,
+ VAddr start) const {
const VAddr text_start{start + nro_header.segment_headers[TEXT_INDEX].memory_offset};
const VAddr ro_start{start + nro_header.segment_headers[RO_INDEX].memory_offset};
const VAddr data_start{start + nro_header.segment_headers[DATA_INDEX].memory_offset};
@@ -528,22 +568,26 @@ public:
rb.Push(*map_result);
}
- ResultCode UnmapNro(const NROInfo& info) {
+ Result UnmapNro(const NROInfo& info) {
// Each region must be unmapped separately to validate memory state
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;
}
@@ -597,6 +641,7 @@ public:
LOG_WARNING(Service_LDR, "(STUBBED) called");
initialized = true;
+ current_map_addr = system.CurrentProcess()->PageTable().GetAliasCodeRegionStart();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -607,6 +652,7 @@ private:
std::map<VAddr, NROInfo> nro;
std::map<VAddr, std::vector<SHA256Hash>> nrr;
+ VAddr current_map_addr{};
bool IsValidNROHash(const SHA256Hash& hash) const {
return std::any_of(nrr.begin(), nrr.end(), [&hash](const auto& p) {
diff --git a/src/core/hle/service/ldr/ldr.h b/src/core/hle/service/ldr/ldr.h
index 104fc15c5..25ffd8442 100644
--- a/src/core/hle/service/ldr/ldr.h
+++ b/src/core/hle/service/ldr/ldr.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp
index e40383134..ef4b54046 100644
--- a/src/core/hle/service/lm/lm.cpp
+++ b/src/core/hle/service/lm/lm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
diff --git a/src/core/hle/service/lm/lm.h b/src/core/hle/service/lm/lm.h
index d40410b5c..266019c30 100644
--- a/src/core/hle/service/lm/lm.h
+++ b/src/core/hle/service/lm/lm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/mig/mig.cpp b/src/core/hle/service/mig/mig.cpp
index 1599d941b..b9fe0cecd 100644
--- a/src/core/hle/service/mig/mig.cpp
+++ b/src/core/hle/service/mig/mig.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
diff --git a/src/core/hle/service/mig/mig.h b/src/core/hle/service/mig/mig.h
index 2b24cdf2c..f1641a521 100644
--- a/src/core/hle/service/mig/mig.h
+++ b/src/core/hle/service/mig/mig.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp
index 0b907824d..390514fdc 100644
--- a/src/core/hle/service/mii/mii.cpp
+++ b/src/core/hle/service/mii/mii.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
@@ -13,7 +12,7 @@
namespace Service::Mii {
-constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1};
+constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1};
class IDatabaseService final : public ServiceFramework<IDatabaseService> {
public:
@@ -44,7 +43,7 @@ public:
{20, nullptr, "IsBrokenDatabaseWithClearFlag"},
{21, &IDatabaseService::GetIndex, "GetIndex"},
{22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"},
- {23, nullptr, "Convert"},
+ {23, &IDatabaseService::Convert, "Convert"},
{24, nullptr, "ConvertCoreDataToCharInfo"},
{25, nullptr, "ConvertCharInfoToCoreData"},
{26, nullptr, "Append"},
@@ -131,7 +130,7 @@ private:
return;
}
- std::vector<MiiInfo> values;
+ std::vector<CharInfo> values;
for (const auto& element : *result) {
values.emplace_back(element.info);
}
@@ -145,7 +144,7 @@ private:
void UpdateLatest(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto info{rp.PopRaw<MiiInfo>()};
+ const auto info{rp.PopRaw<CharInfo>()};
const auto source_flag{rp.PopRaw<SourceFlag>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
@@ -157,9 +156,9 @@ private:
return;
}
- IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
- rb.PushRaw<MiiInfo>(*result);
+ rb.PushRaw<CharInfo>(*result);
}
void BuildRandom(Kernel::HLERequestContext& ctx) {
@@ -192,9 +191,9 @@ private:
return;
}
- IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
- rb.PushRaw<MiiInfo>(manager.BuildRandom(age, gender, race));
+ rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race));
}
void BuildDefault(Kernel::HLERequestContext& ctx) {
@@ -211,14 +210,14 @@ private:
return;
}
- IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
- rb.PushRaw<MiiInfo>(manager.BuildDefault(index));
+ rb.PushRaw<CharInfo>(manager.BuildDefault(index));
}
void GetIndex(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto info{rp.PopRaw<MiiInfo>()};
+ const auto info{rp.PopRaw<CharInfo>()};
LOG_DEBUG(Service_Mii, "called");
@@ -240,6 +239,18 @@ private:
rb.Push(ResultSuccess);
}
+ void Convert(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const auto mii_v3{rp.PopRaw<Ver3StoreData>()};
+
+ LOG_INFO(Service_Mii, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
+ rb.Push(ResultSuccess);
+ rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3));
+ }
+
constexpr bool IsInterfaceVersionSupported(u32 interface_version) const {
return current_interface_version >= interface_version;
}
diff --git a/src/core/hle/service/mii/mii.h b/src/core/hle/service/mii/mii.h
index 9d3238e72..009d80d58 100644
--- a/src/core/hle/service/mii/mii.h
+++ b/src/core/hle/service/mii/mii.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index 0a57c3cde..3a2fe938f 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <random>
@@ -12,13 +11,12 @@
#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 {
namespace {
-constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
+constexpr Result ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
constexpr std::size_t BaseMiiCount{2};
constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
@@ -44,7 +42,7 @@ std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& i
return out;
}
-MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
+CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
MiiStoreBitFields bf;
std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields));
@@ -292,7 +290,7 @@ MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Commo
u8 glasses_type{};
while (glasses_type_start < glasses_type_info.values[glasses_type]) {
if (++glasses_type >= glasses_type_info.values_count) {
- UNREACHABLE();
+ ASSERT(false);
break;
}
}
@@ -411,8 +409,8 @@ u32 MiiManager::GetCount(SourceFlag source_flag) const {
return static_cast<u32>(count);
}
-ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info,
- SourceFlag source_flag) {
+ResultVal<CharInfo> MiiManager::UpdateLatest([[maybe_unused]] const CharInfo& info,
+ SourceFlag source_flag) {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return ERROR_CANNOT_FIND_ENTRY;
}
@@ -421,14 +419,242 @@ ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info
return ERROR_CANNOT_FIND_ENTRY;
}
-MiiInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) {
+CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) {
return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id));
}
-MiiInfo MiiManager::BuildDefault(std::size_t index) {
+CharInfo MiiManager::BuildDefault(std::size_t index) {
return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id));
}
+CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const {
+ Service::Mii::MiiManager manager;
+ auto mii = manager.BuildDefault(0);
+
+ if (!ValidateV3Info(mii_v3)) {
+ return mii;
+ }
+
+ // TODO: We are ignoring a bunch of data from the mii_v3
+
+ mii.gender = static_cast<u8>(mii_v3.mii_information.gender);
+ mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color);
+ mii.height = mii_v3.height;
+ mii.build = mii_v3.build;
+
+ // Copy name until string terminator
+ mii.name = {};
+ for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
+ mii.name[index] = mii_v3.mii_name[index];
+ if (mii.name[index] == 0) {
+ break;
+ }
+ }
+
+ mii.font_region = mii_v3.region_information.character_set;
+
+ mii.faceline_type = mii_v3.appearance_bits1.face_shape;
+ mii.faceline_color = mii_v3.appearance_bits1.skin_color;
+ mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles;
+ mii.faceline_make = mii_v3.appearance_bits2.makeup;
+
+ mii.hair_type = mii_v3.hair_style;
+ mii.hair_color = mii_v3.appearance_bits3.hair_color;
+ mii.hair_flip = mii_v3.appearance_bits3.flip_hair;
+
+ mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type);
+ mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color);
+ mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale);
+ mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch);
+ mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation);
+ mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing);
+ mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position);
+
+ mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style);
+ mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color);
+ mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale);
+ mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale);
+ mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation);
+ mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing);
+ mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position);
+
+ mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type);
+ mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale);
+ mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position);
+
+ mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type);
+ mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color);
+ mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale);
+ mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch);
+ mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position);
+
+ mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type);
+ mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale);
+ mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position);
+
+ mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type);
+ mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color);
+
+ mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type);
+ mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color);
+ mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale);
+ mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position);
+
+ mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled);
+ mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale);
+ mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position);
+ mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position);
+
+ // TODO: Validate mii data
+
+ return mii;
+}
+
+Ver3StoreData MiiManager::ConvertCharInfoToV3(const CharInfo& mii) const {
+ Service::Mii::MiiManager manager;
+ Ver3StoreData mii_v3{};
+
+ // TODO: We are ignoring a bunch of data from the mii_v3
+
+ mii_v3.version = 1;
+ mii_v3.mii_information.gender.Assign(mii.gender);
+ mii_v3.mii_information.favorite_color.Assign(mii.favorite_color);
+ mii_v3.height = mii.height;
+ mii_v3.build = mii.build;
+
+ // Copy name until string terminator
+ mii_v3.mii_name = {};
+ for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
+ mii_v3.mii_name[index] = mii.name[index];
+ if (mii_v3.mii_name[index] == 0) {
+ break;
+ }
+ }
+
+ mii_v3.region_information.character_set.Assign(mii.font_region);
+
+ mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type);
+ mii_v3.appearance_bits1.skin_color.Assign(mii.faceline_color);
+ mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle);
+ mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make);
+
+ mii_v3.hair_style = mii.hair_type;
+ mii_v3.appearance_bits3.hair_color.Assign(mii.hair_color);
+ mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip);
+
+ mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type);
+ mii_v3.appearance_bits4.eye_color.Assign(mii.eye_color);
+ mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale);
+ mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect);
+ mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate);
+ mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x);
+ mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y);
+
+ mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type);
+ mii_v3.appearance_bits5.eyebrow_color.Assign(mii.eyebrow_color);
+ mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale);
+ mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect);
+ mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate);
+ mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x);
+ mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y);
+
+ mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type);
+ mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale);
+ mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y);
+
+ mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type);
+ mii_v3.appearance_bits7.mouth_color.Assign(mii.mouth_color);
+ mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale);
+ mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect);
+ mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y);
+
+ mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type);
+ mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale);
+ mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y);
+
+ mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type);
+ mii_v3.appearance_bits9.facial_hair_color.Assign(mii.beard_color);
+
+ mii_v3.appearance_bits10.glasses_type.Assign(mii.glasses_type);
+ mii_v3.appearance_bits10.glasses_color.Assign(mii.glasses_color);
+ mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale);
+ mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y);
+
+ mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type);
+ mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale);
+ mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x);
+ mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y);
+
+ // TODO: Validate mii_v3 data
+
+ return mii_v3;
+}
+
+bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const {
+ bool is_valid = mii_v3.version == 0 || mii_v3.version == 3;
+
+ is_valid = is_valid && (mii_v3.mii_name[0] != 0);
+
+ is_valid = is_valid && (mii_v3.mii_information.birth_month < 13);
+ is_valid = is_valid && (mii_v3.mii_information.birth_day < 32);
+ is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12);
+ is_valid = is_valid && (mii_v3.height < 128);
+ is_valid = is_valid && (mii_v3.build < 128);
+
+ is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12);
+ is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7);
+ is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12);
+ is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12);
+
+ is_valid = is_valid && (mii_v3.hair_style < 132);
+ is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8);
+
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60);
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6);
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8);
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7);
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8);
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13);
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19);
+
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25);
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8);
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9);
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7);
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12);
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12);
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19);
+
+ is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18);
+ is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9);
+ is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19);
+
+ is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36);
+ is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5);
+ is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9);
+ is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7);
+ is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19);
+
+ is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6);
+ is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7);
+ is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17);
+
+ is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6);
+ is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8);
+
+ is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9);
+ is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6);
+ is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8);
+ is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21);
+
+ is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2);
+ is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9);
+ is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17);
+ is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31);
+
+ return is_valid;
+}
+
ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
std::vector<MiiInfoElement> result;
@@ -443,7 +669,7 @@ ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_
return result;
}
-ResultCode MiiManager::GetIndex([[maybe_unused]] const MiiInfo& info, u32& index) {
+Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) {
constexpr u32 INVALID_INDEX{0xFFFFFFFF};
index = INVALID_INDEX;
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
index 6999d15b1..83ad3d343 100644
--- a/src/core/hle/service/mii/mii_manager.h
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -1,315 +1,15 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#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 {
@@ -319,11 +19,14 @@ public:
bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter);
bool IsFullDatabase() const;
u32 GetCount(SourceFlag source_flag) const;
- ResultVal<MiiInfo> UpdateLatest(const MiiInfo& info, SourceFlag source_flag);
- MiiInfo BuildRandom(Age age, Gender gender, Race race);
- MiiInfo BuildDefault(std::size_t index);
+ ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag);
+ CharInfo BuildRandom(Age age, Gender gender, Race race);
+ CharInfo BuildDefault(std::size_t index);
+ CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const;
+ Ver3StoreData ConvertCharInfoToV3(const CharInfo& mii) const;
+ bool ValidateV3Info(const Ver3StoreData& mii_v3) const;
ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag);
- ResultCode GetIndex(const MiiInfo& info, u32& index);
+ Result GetIndex(const CharInfo& info, u32& index);
private:
const Common::UUID user_id{};
diff --git a/src/core/hle/service/mii/raw_data.cpp b/src/core/hle/service/mii/raw_data.cpp
index 9d3c8017a..1442280c8 100644
--- a/src/core/hle/service/mii/raw_data.cpp
+++ b/src/core/hle/service/mii/raw_data.cpp
@@ -1,22 +1,5 @@
-// MIT License
-//
-// Copyright (c) Ryujinx Team and Contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
-// associated documentation files (the "Software"), to deal in the Software without restriction,
-// including without limitation the rights to use, copy, modify, merge, publish, distribute,
-// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all copies or
-// substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
-// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
+// SPDX-FileCopyrightText: Ryujinx Team and Contributors
+// SPDX-License-Identifier: MIT
#include "core/hle/service/mii/raw_data.h"
diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h
index bd90c2162..c2bec68d4 100644
--- a/src/core/hle/service/mii/raw_data.h
+++ b/src/core/hle/service/mii/raw_data.h
@@ -1,12 +1,11 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#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..9e3247397 100644
--- a/src/core/hle/service/mii/types.h
+++ b/src/core/hle/service/mii/types.h
@@ -1,11 +1,15 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#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 +29,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 +72,424 @@ 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);
+
+// nn::mii::CharInfo
+struct CharInfo {
+ 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(CharInfo) == 0x58, "CharInfo has incorrect size.");
+static_assert(std::has_unique_object_representations_v<CharInfo>,
+ "All bits of CharInfo must contribute to its value.");
+
+#pragma pack(push, 4)
+
+struct MiiInfoElement {
+ MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {}
+
+ CharInfo 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.");
+
+// This is nn::mii::Ver3StoreData
+// Based on citra HLE::Applets::MiiData and PretendoNetwork.
+// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
+// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
+struct Ver3StoreData {
+ u8 version;
+ union {
+ u8 raw;
+
+ BitField<0, 1, u8> allow_copying;
+ BitField<1, 1, u8> profanity_flag;
+ BitField<2, 2, u8> region_lock;
+ BitField<4, 2, u8> character_set;
+ } region_information;
+ u16_be mii_id;
+ u64_be system_id;
+ u32_be specialness_and_creation_date;
+ std::array<u8, 0x6> creator_mac;
+ u16_be padding;
+ union {
+ u16 raw;
+
+ BitField<0, 1, u16> gender;
+ BitField<1, 4, u16> birth_month;
+ BitField<5, 5, u16> birth_day;
+ BitField<10, 4, u16> favorite_color;
+ BitField<14, 1, u16> favorite;
+ } mii_information;
+ std::array<char16_t, 0xA> mii_name;
+ u8 height;
+ u8 build;
+ union {
+ u8 raw;
+
+ BitField<0, 1, u8> disable_sharing;
+ BitField<1, 4, u8> face_shape;
+ BitField<5, 3, u8> skin_color;
+ } appearance_bits1;
+ union {
+ u8 raw;
+
+ BitField<0, 4, u8> wrinkles;
+ BitField<4, 4, u8> makeup;
+ } appearance_bits2;
+ u8 hair_style;
+ union {
+ u8 raw;
+
+ BitField<0, 3, u8> hair_color;
+ BitField<3, 1, u8> flip_hair;
+ } appearance_bits3;
+ union {
+ u32 raw;
+
+ BitField<0, 6, u32> eye_type;
+ BitField<6, 3, u32> eye_color;
+ BitField<9, 4, u32> eye_scale;
+ BitField<13, 3, u32> eye_vertical_stretch;
+ BitField<16, 5, u32> eye_rotation;
+ BitField<21, 4, u32> eye_spacing;
+ BitField<25, 5, u32> eye_y_position;
+ } appearance_bits4;
+ union {
+ u32 raw;
+
+ BitField<0, 5, u32> eyebrow_style;
+ BitField<5, 3, u32> eyebrow_color;
+ BitField<8, 4, u32> eyebrow_scale;
+ BitField<12, 3, u32> eyebrow_yscale;
+ BitField<16, 4, u32> eyebrow_rotation;
+ BitField<21, 4, u32> eyebrow_spacing;
+ BitField<25, 5, u32> eyebrow_y_position;
+ } appearance_bits5;
+ union {
+ u16 raw;
+
+ BitField<0, 5, u16> nose_type;
+ BitField<5, 4, u16> nose_scale;
+ BitField<9, 5, u16> nose_y_position;
+ } appearance_bits6;
+ union {
+ u16 raw;
+
+ BitField<0, 6, u16> mouth_type;
+ BitField<6, 3, u16> mouth_color;
+ BitField<9, 4, u16> mouth_scale;
+ BitField<13, 3, u16> mouth_horizontal_stretch;
+ } appearance_bits7;
+ union {
+ u8 raw;
+
+ BitField<0, 5, u8> mouth_y_position;
+ BitField<5, 3, u8> mustache_type;
+ } appearance_bits8;
+ u8 allow_copying;
+ union {
+ u16 raw;
+
+ BitField<0, 3, u16> bear_type;
+ BitField<3, 3, u16> facial_hair_color;
+ BitField<6, 4, u16> mustache_scale;
+ BitField<10, 5, u16> mustache_y_position;
+ } appearance_bits9;
+ union {
+ u16 raw;
+
+ BitField<0, 4, u16> glasses_type;
+ BitField<4, 3, u16> glasses_color;
+ BitField<7, 4, u16> glasses_scale;
+ BitField<11, 5, u16> glasses_y_position;
+ } appearance_bits10;
+ union {
+ u16 raw;
+
+ BitField<0, 1, u16> mole_enabled;
+ BitField<1, 4, u16> mole_scale;
+ BitField<5, 5, u16> mole_x_position;
+ BitField<10, 5, u16> mole_y_position;
+ } appearance_bits11;
+
+ std::array<u16_le, 0xA> author_name;
+ INSERT_PADDING_BYTES(0x4);
+};
+static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
+
+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/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp
index 0ccfe91cd..ba8c0e230 100644
--- a/src/core/hle/service/mm/mm_u.cpp
+++ b/src/core/hle/service/mm/mm_u.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
@@ -47,7 +46,7 @@ private:
IPC::RequestParser rp{ctx};
min = rp.Pop<u32>();
max = rp.Pop<u32>();
- LOG_WARNING(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max);
+ LOG_DEBUG(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max);
current = min;
IPC::ResponseBuilder rb{ctx, 2};
@@ -55,7 +54,7 @@ private:
}
void GetOld(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_MM, "(STUBBED) called");
+ LOG_DEBUG(Service_MM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@@ -82,8 +81,8 @@ private:
u32 input_id = rp.Pop<u32>();
min = rp.Pop<u32>();
max = rp.Pop<u32>();
- LOG_WARNING(Service_MM, "(STUBBED) called, input_id=0x{:X}, min=0x{:X}, max=0x{:X}",
- input_id, min, max);
+ LOG_DEBUG(Service_MM, "(STUBBED) called, input_id=0x{:X}, min=0x{:X}, max=0x{:X}", input_id,
+ min, max);
current = min;
IPC::ResponseBuilder rb{ctx, 2};
@@ -91,7 +90,7 @@ private:
}
void Get(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_MM, "(STUBBED) called");
+ LOG_DEBUG(Service_MM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/mm/mm_u.h b/src/core/hle/service/mm/mm_u.h
index 49b6a3355..b40941e35 100644
--- a/src/core/hle/service/mm/mm_u.h
+++ b/src/core/hle/service/mm/mm_u.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/mnpp/mnpp_app.cpp b/src/core/hle/service/mnpp/mnpp_app.cpp
new file mode 100644
index 000000000..c3aad5714
--- /dev/null
+++ b/src/core/hle/service/mnpp/mnpp_app.cpp
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/mnpp/mnpp_app.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Service::MNPP {
+
+class MNPP_APP final : public ServiceFramework<MNPP_APP> {
+public:
+ explicit MNPP_APP(Core::System& system_) : ServiceFramework{system_, "mnpp:app"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &MNPP_APP::Unknown0, "unknown0"},
+ {1, &MNPP_APP::Unknown1, "unknown1"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void Unknown0(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_MNPP, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void Unknown1(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_MNPP, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+};
+
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+ std::make_shared<MNPP_APP>(system)->InstallAsService(service_manager);
+}
+
+} // namespace Service::MNPP
diff --git a/src/core/hle/service/mnpp/mnpp_app.h b/src/core/hle/service/mnpp/mnpp_app.h
new file mode 100644
index 000000000..eec75fe0e
--- /dev/null
+++ b/src/core/hle/service/mnpp/mnpp_app.h
@@ -0,0 +1,19 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+namespace Core {
+class System;
+}
+
+namespace Service::SM {
+class ServiceManager;
+}
+
+namespace Service::MNPP {
+
+/// Registers all MNPP services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
+
+} // namespace Service::MNPP
diff --git a/src/core/hle/service/ncm/ncm.cpp b/src/core/hle/service/ncm/ncm.cpp
index 2dcda16f6..68210a108 100644
--- a/src/core/hle/service/ncm/ncm.cpp
+++ b/src/core/hle/service/ncm/ncm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
diff --git a/src/core/hle/service/ncm/ncm.h b/src/core/hle/service/ncm/ncm.h
index ee01eddc0..de3971437 100644
--- a/src/core/hle/service/ncm/ncm.h
+++ b/src/core/hle/service/ncm/ncm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index f77037842..046c5f18f 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
@@ -107,10 +106,10 @@ public:
{1, &IUser::FinalizeOld, "FinalizeOld"},
{2, &IUser::GetStateOld, "GetStateOld"},
{3, &IUser::IsNfcEnabledOld, "IsNfcEnabledOld"},
- {400, nullptr, "Initialize"},
- {401, nullptr, "Finalize"},
- {402, nullptr, "GetState"},
- {403, nullptr, "IsNfcEnabled"},
+ {400, &IUser::InitializeOld, "Initialize"},
+ {401, &IUser::FinalizeOld, "Finalize"},
+ {402, &IUser::GetStateOld, "GetState"},
+ {403, &IUser::IsNfcEnabledOld, "IsNfcEnabled"},
{404, nullptr, "ListDevices"},
{405, nullptr, "GetDeviceState"},
{406, nullptr, "GetNpadId"},
diff --git a/src/core/hle/service/nfc/nfc.h b/src/core/hle/service/nfc/nfc.h
index 5a94b076d..0107b696c 100644
--- a/src/core/hle/service/nfc/nfc.h
+++ b/src/core/hle/service/nfc/nfc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp
new file mode 100644
index 000000000..c32a6816b
--- /dev/null
+++ b/src/core/hle/service/nfp/amiibo_crypto.cpp
@@ -0,0 +1,388 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// SPDX-FileCopyrightText: Copyright 2017 socram8888/amiitool
+// SPDX-License-Identifier: MIT
+
+#include <array>
+#include <mbedtls/aes.h>
+#include <mbedtls/hmac_drbg.h>
+
+#include "common/fs/file.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/nfp/amiibo_crypto.h"
+
+namespace Service::NFP::AmiiboCrypto {
+
+bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
+ const auto& amiibo_data = ntag_file.user_memory;
+ LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock);
+ LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", ntag_file.compability_container);
+ LOG_DEBUG(Service_NFP, "write_count={}", static_cast<u16>(amiibo_data.write_counter));
+
+ LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
+ LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
+ LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
+ LOG_DEBUG(Service_NFP, "model_number=0x{0:x}",
+ static_cast<u16>(amiibo_data.model_info.model_number));
+ LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series);
+ LOG_DEBUG(Service_NFP, "tag_type=0x{0:x}", amiibo_data.model_info.tag_type);
+
+ LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock);
+ LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", ntag_file.CFG0);
+ LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", ntag_file.CFG1);
+
+ // Validate UUID
+ constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3`
+ if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) !=
+ ntag_file.uuid.uid[3]) {
+ return false;
+ }
+ if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^
+ ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) {
+ return false;
+ }
+
+ // Check against all know constants on an amiibo binary
+ if (ntag_file.static_lock != 0xE00F) {
+ return false;
+ }
+ if (ntag_file.compability_container != 0xEEFF10F1U) {
+ return false;
+ }
+ if (amiibo_data.constant_value != 0xA5) {
+ return false;
+ }
+ if (amiibo_data.model_info.tag_type != PackedTagType::Type2) {
+ return false;
+ }
+ if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001U) {
+ return false;
+ }
+ if (ntag_file.CFG0 != 0x04000000U) {
+ return false;
+ }
+ if (ntag_file.CFG1 != 0x5F) {
+ return false;
+ }
+ return true;
+}
+
+NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
+ NTAG215File encoded_data{};
+
+ encoded_data.uid = nfc_data.uuid.uid;
+ encoded_data.nintendo_id = nfc_data.uuid.nintendo_id;
+ encoded_data.static_lock = nfc_data.static_lock;
+ encoded_data.compability_container = nfc_data.compability_container;
+ encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
+ encoded_data.constant_value = nfc_data.user_memory.constant_value;
+ encoded_data.write_counter = nfc_data.user_memory.write_counter;
+ encoded_data.settings = nfc_data.user_memory.settings;
+ encoded_data.owner_mii = nfc_data.user_memory.owner_mii;
+ encoded_data.title_id = nfc_data.user_memory.title_id;
+ encoded_data.applicaton_write_counter = nfc_data.user_memory.applicaton_write_counter;
+ encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
+ encoded_data.unknown = nfc_data.user_memory.unknown;
+ encoded_data.unknown2 = nfc_data.user_memory.unknown2;
+ encoded_data.application_area = nfc_data.user_memory.application_area;
+ encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
+ encoded_data.lock_bytes = nfc_data.uuid.lock_bytes;
+ encoded_data.model_info = nfc_data.user_memory.model_info;
+ encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt;
+ encoded_data.dynamic_lock = nfc_data.dynamic_lock;
+ encoded_data.CFG0 = nfc_data.CFG0;
+ encoded_data.CFG1 = nfc_data.CFG1;
+ encoded_data.password = nfc_data.password;
+
+ return encoded_data;
+}
+
+EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
+ EncryptedNTAG215File nfc_data{};
+
+ nfc_data.uuid.uid = encoded_data.uid;
+ nfc_data.uuid.nintendo_id = encoded_data.nintendo_id;
+ nfc_data.uuid.lock_bytes = encoded_data.lock_bytes;
+ nfc_data.static_lock = encoded_data.static_lock;
+ nfc_data.compability_container = encoded_data.compability_container;
+ nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
+ nfc_data.user_memory.constant_value = encoded_data.constant_value;
+ nfc_data.user_memory.write_counter = encoded_data.write_counter;
+ nfc_data.user_memory.settings = encoded_data.settings;
+ nfc_data.user_memory.owner_mii = encoded_data.owner_mii;
+ nfc_data.user_memory.title_id = encoded_data.title_id;
+ nfc_data.user_memory.applicaton_write_counter = encoded_data.applicaton_write_counter;
+ nfc_data.user_memory.application_area_id = encoded_data.application_area_id;
+ nfc_data.user_memory.unknown = encoded_data.unknown;
+ nfc_data.user_memory.unknown2 = encoded_data.unknown2;
+ nfc_data.user_memory.application_area = encoded_data.application_area;
+ nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag;
+ nfc_data.user_memory.model_info = encoded_data.model_info;
+ nfc_data.user_memory.keygen_salt = encoded_data.keygen_salt;
+ nfc_data.dynamic_lock = encoded_data.dynamic_lock;
+ nfc_data.CFG0 = encoded_data.CFG0;
+ nfc_data.CFG1 = encoded_data.CFG1;
+ nfc_data.password = encoded_data.password;
+
+ return nfc_data;
+}
+
+u32 GetTagPassword(const TagUuid& uuid) {
+ // Verifiy that the generated password is correct
+ u32 password = 0xAA ^ (uuid.uid[1] ^ uuid.uid[3]);
+ password &= (0x55 ^ (uuid.uid[2] ^ uuid.uid[4])) << 8;
+ password &= (0xAA ^ (uuid.uid[3] ^ uuid.uid[5])) << 16;
+ password &= (0x55 ^ (uuid.uid[4] ^ uuid.uid[6])) << 24;
+ return password;
+}
+
+HashSeed GetSeed(const NTAG215File& data) {
+ HashSeed seed{
+ .magic = data.write_counter,
+ .padding = {},
+ .uid_1 = data.uid,
+ .nintendo_id_1 = data.nintendo_id,
+ .uid_2 = data.uid,
+ .nintendo_id_2 = data.nintendo_id,
+ .keygen_salt = data.keygen_salt,
+ };
+
+ return seed;
+}
+
+std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) {
+ const std::size_t seedPart1Len = sizeof(key.magic_bytes) - key.magic_length;
+ const std::size_t string_size = key.type_string.size();
+ std::vector<u8> output(string_size + seedPart1Len);
+
+ // Copy whole type string
+ memccpy(output.data(), key.type_string.data(), '\0', string_size);
+
+ // Append (16 - magic_length) from the input seed
+ memcpy(output.data() + string_size, &seed, seedPart1Len);
+
+ // Append all bytes from magicBytes
+ output.insert(output.end(), key.magic_bytes.begin(),
+ key.magic_bytes.begin() + key.magic_length);
+
+ output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end());
+ output.emplace_back(seed.nintendo_id_1);
+ output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end());
+ output.emplace_back(seed.nintendo_id_2);
+
+ for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) {
+ output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i]));
+ }
+
+ return output;
+}
+
+void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
+ const std::vector<u8>& seed) {
+ // Initialize context
+ ctx.used = false;
+ ctx.counter = 0;
+ ctx.buffer_size = sizeof(ctx.counter) + seed.size();
+ memcpy(ctx.buffer.data() + sizeof(u16), seed.data(), seed.size());
+
+ // Initialize HMAC context
+ mbedtls_md_init(&hmac_ctx);
+ mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
+ mbedtls_md_hmac_starts(&hmac_ctx, hmac_key.data(), hmac_key.size());
+}
+
+void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output) {
+ // If used at least once, reinitialize the HMAC
+ if (ctx.used) {
+ mbedtls_md_hmac_reset(&hmac_ctx);
+ }
+
+ ctx.used = true;
+
+ // Store counter in big endian, and increment it
+ ctx.buffer[0] = static_cast<u8>(ctx.counter >> 8);
+ ctx.buffer[1] = static_cast<u8>(ctx.counter >> 0);
+ ctx.counter++;
+
+ // Do HMAC magic
+ mbedtls_md_hmac_update(&hmac_ctx, reinterpret_cast<const unsigned char*>(ctx.buffer.data()),
+ ctx.buffer_size);
+ mbedtls_md_hmac_finish(&hmac_ctx, output.data());
+}
+
+DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) {
+ const auto seed = GetSeed(data);
+
+ // Generate internal seed
+ const std::vector<u8> internal_key = GenerateInternalKey(key, seed);
+
+ // Initialize context
+ CryptoCtx ctx{};
+ mbedtls_md_context_t hmac_ctx;
+ CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key);
+
+ // Generate derived keys
+ DerivedKeys derived_keys{};
+ std::array<DrgbOutput, 2> temp{};
+ CryptoStep(ctx, hmac_ctx, temp[0]);
+ CryptoStep(ctx, hmac_ctx, temp[1]);
+ memcpy(&derived_keys, temp.data(), sizeof(DerivedKeys));
+
+ // Cleanup context
+ mbedtls_md_free(&hmac_ctx);
+
+ return derived_keys;
+}
+
+void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) {
+ mbedtls_aes_context aes;
+ std::size_t nc_off = 0;
+ std::array<u8, sizeof(keys.aes_iv)> nonce_counter{};
+ std::array<u8, sizeof(keys.aes_iv)> stream_block{};
+
+ const auto aes_key_size = static_cast<u32>(keys.aes_key.size() * 8);
+ mbedtls_aes_setkey_enc(&aes, keys.aes_key.data(), aes_key_size);
+ memcpy(nonce_counter.data(), keys.aes_iv.data(), sizeof(keys.aes_iv));
+
+ constexpr std::size_t encrypted_data_size = HMAC_TAG_START - SETTINGS_START;
+ mbedtls_aes_crypt_ctr(&aes, encrypted_data_size, &nc_off, nonce_counter.data(),
+ stream_block.data(),
+ reinterpret_cast<const unsigned char*>(&in_data.settings),
+ reinterpret_cast<unsigned char*>(&out_data.settings));
+
+ // Copy the rest of the data directly
+ out_data.uid = in_data.uid;
+ out_data.nintendo_id = in_data.nintendo_id;
+ out_data.lock_bytes = in_data.lock_bytes;
+ out_data.static_lock = in_data.static_lock;
+ out_data.compability_container = in_data.compability_container;
+
+ out_data.constant_value = in_data.constant_value;
+ out_data.write_counter = in_data.write_counter;
+
+ out_data.model_info = in_data.model_info;
+ out_data.keygen_salt = in_data.keygen_salt;
+ out_data.dynamic_lock = in_data.dynamic_lock;
+ out_data.CFG0 = in_data.CFG0;
+ out_data.CFG1 = in_data.CFG1;
+ out_data.password = in_data.password;
+}
+
+bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) {
+ const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
+
+ const Common::FS::IOFile keys_file{yuzu_keys_dir / "key_retail.bin",
+ Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+
+ if (!keys_file.IsOpen()) {
+ LOG_ERROR(Service_NFP, "No keys detected");
+ return false;
+ }
+
+ if (keys_file.Read(unfixed_info) != 1) {
+ LOG_ERROR(Service_NFP, "Failed to read unfixed_info");
+ return false;
+ }
+ if (keys_file.Read(locked_secret) != 1) {
+ LOG_ERROR(Service_NFP, "Failed to read locked-secret");
+ return false;
+ }
+
+ return true;
+}
+
+bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) {
+ InternalKey locked_secret{};
+ InternalKey unfixed_info{};
+
+ if (!LoadKeys(locked_secret, unfixed_info)) {
+ return false;
+ }
+
+ // Generate keys
+ NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data);
+ const auto data_keys = GenerateKey(unfixed_info, encoded_data);
+ const auto tag_keys = GenerateKey(locked_secret, encoded_data);
+
+ // Decrypt
+ Cipher(data_keys, encoded_data, tag_data);
+
+ // Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
+ constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
+ mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
+ sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
+ input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag));
+
+ // Regenerate data HMAC
+ constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START;
+ mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), data_keys.hmac_key.data(),
+ sizeof(HmacKey),
+ reinterpret_cast<const unsigned char*>(&tag_data.write_counter), input_length2,
+ reinterpret_cast<unsigned char*>(&tag_data.hmac_data));
+
+ if (tag_data.hmac_data != encrypted_tag_data.user_memory.hmac_data) {
+ LOG_ERROR(Service_NFP, "hmac_data doesn't match");
+ return false;
+ }
+
+ if (tag_data.hmac_tag != encrypted_tag_data.user_memory.hmac_tag) {
+ LOG_ERROR(Service_NFP, "hmac_tag doesn't match");
+ return false;
+ }
+
+ return true;
+}
+
+bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data) {
+ InternalKey locked_secret{};
+ InternalKey unfixed_info{};
+
+ if (!LoadKeys(locked_secret, unfixed_info)) {
+ return false;
+ }
+
+ // Generate keys
+ const auto data_keys = GenerateKey(unfixed_info, tag_data);
+ const auto tag_keys = GenerateKey(locked_secret, tag_data);
+
+ NTAG215File encoded_tag_data{};
+
+ // Generate tag HMAC
+ constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
+ constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START;
+ mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
+ sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
+ input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag));
+
+ // Init mbedtls HMAC context
+ mbedtls_md_context_t ctx;
+ mbedtls_md_init(&ctx);
+ mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
+
+ // Generate data HMAC
+ mbedtls_md_hmac_starts(&ctx, data_keys.hmac_key.data(), sizeof(HmacKey));
+ mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.write_counter),
+ input_length2); // Data
+ mbedtls_md_hmac_update(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
+ sizeof(HashData)); // Tag HMAC
+ mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uid),
+ input_length);
+ mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data));
+
+ // HMAC cleanup
+ mbedtls_md_free(&ctx);
+
+ // Encrypt
+ Cipher(data_keys, tag_data, encoded_tag_data);
+
+ // Convert back to hardware
+ encrypted_tag_data = EncodedDataToNfcData(encoded_tag_data);
+
+ return true;
+}
+
+} // namespace Service::NFP::AmiiboCrypto
diff --git a/src/core/hle/service/nfp/amiibo_crypto.h b/src/core/hle/service/nfp/amiibo_crypto.h
new file mode 100644
index 000000000..0175ced91
--- /dev/null
+++ b/src/core/hle/service/nfp/amiibo_crypto.h
@@ -0,0 +1,100 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "core/hle/service/nfp/nfp_types.h"
+
+struct mbedtls_md_context_t;
+
+namespace Service::NFP::AmiiboCrypto {
+// Byte locations in Service::NFP::NTAG215File
+constexpr std::size_t HMAC_DATA_START = 0x8;
+constexpr std::size_t SETTINGS_START = 0x2c;
+constexpr std::size_t WRITE_COUNTER_START = 0x29;
+constexpr std::size_t HMAC_TAG_START = 0x1B4;
+constexpr std::size_t UUID_START = 0x1D4;
+constexpr std::size_t DYNAMIC_LOCK_START = 0x208;
+
+using HmacKey = std::array<u8, 0x10>;
+using DrgbOutput = std::array<u8, 0x20>;
+
+struct HashSeed {
+ u16_be magic;
+ std::array<u8, 0xE> padding;
+ UniqueSerialNumber uid_1;
+ u8 nintendo_id_1;
+ UniqueSerialNumber uid_2;
+ u8 nintendo_id_2;
+ std::array<u8, 0x20> keygen_salt;
+};
+static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");
+
+struct InternalKey {
+ HmacKey hmac_key;
+ std::array<char, 0xE> type_string;
+ u8 reserved;
+ u8 magic_length;
+ std::array<u8, 0x10> magic_bytes;
+ std::array<u8, 0x20> xor_pad;
+};
+static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size");
+static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable.");
+
+struct CryptoCtx {
+ std::array<char, 480> buffer;
+ bool used;
+ std::size_t buffer_size;
+ s16 counter;
+};
+
+struct DerivedKeys {
+ std::array<u8, 0x10> aes_key;
+ std::array<u8, 0x10> aes_iv;
+ std::array<u8, 0x10> hmac_key;
+};
+static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size");
+
+/// Validates that the amiibo file is not corrupted
+bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file);
+
+/// Converts from encrypted file format to encoded file format
+NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data);
+
+/// Converts from encoded file format to encrypted file format
+EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data);
+
+/// Returns password needed to allow write access to protected memory
+u32 GetTagPassword(const TagUuid& uuid);
+
+// Generates Seed needed for key derivation
+HashSeed GetSeed(const NTAG215File& data);
+
+// Middle step on the generation of derived keys
+std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed);
+
+// Initializes mbedtls context
+void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
+ const std::vector<u8>& seed);
+
+// Feeds data to mbedtls context to generate the derived key
+void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output);
+
+// Generates the derived key from amiibo data
+DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data);
+
+// Encodes or decodes amiibo data
+void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data);
+
+/// Loads both amiibo keys from key_retail.bin
+bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info);
+
+/// Decodes encripted amiibo data returns true if output is valid
+bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data);
+
+/// Encodes plain amiibo data returns true if output is valid
+bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data);
+
+} // namespace Service::NFP::AmiiboCrypto
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 761d0d3c6..0cb55ca49 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -1,361 +1,43 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <array>
-#include <atomic>
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
-#include "core/core.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/k_event.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_user.h"
namespace Service::NFP {
-namespace ErrCodes {
-constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152);
-} // namespace ErrCodes
-
-Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_,
- const char* name)
- : ServiceFramework{system_, name}, module{std::move(module_)}, service_context{system_,
- "NFP::IUser"} {
- nfc_tag_load = service_context.CreateEvent("NFP::IUser:NFCTagDetected");
-}
-
-Module::Interface::~Interface() {
- service_context.CloseEvent(nfc_tag_load);
-}
-class IUser final : public ServiceFramework<IUser> {
+class IUserManager final : public ServiceFramework<IUserManager> {
public:
- explicit IUser(Module::Interface& nfp_interface_, Core::System& system_,
- KernelHelpers::ServiceContext& service_context_)
- : ServiceFramework{system_, "NFP::IUser"}, nfp_interface{nfp_interface_},
- service_context{service_context_} {
+ explicit IUserManager(Core::System& system_) : ServiceFramework{system_, "nfp:user"} {
+ // clang-format off
static const FunctionInfo functions[] = {
- {0, &IUser::Initialize, "Initialize"},
- {1, &IUser::Finalize, "Finalize"},
- {2, &IUser::ListDevices, "ListDevices"},
- {3, &IUser::StartDetection, "StartDetection"},
- {4, &IUser::StopDetection, "StopDetection"},
- {5, &IUser::Mount, "Mount"},
- {6, &IUser::Unmount, "Unmount"},
- {7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
- {8, &IUser::GetApplicationArea, "GetApplicationArea"},
- {9, nullptr, "SetApplicationArea"},
- {10, nullptr, "Flush"},
- {11, nullptr, "Restore"},
- {12, nullptr, "CreateApplicationArea"},
- {13, &IUser::GetTagInfo, "GetTagInfo"},
- {14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
- {15, &IUser::GetCommonInfo, "GetCommonInfo"},
- {16, &IUser::GetModelInfo, "GetModelInfo"},
- {17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
- {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
- {19, &IUser::GetState, "GetState"},
- {20, &IUser::GetDeviceState, "GetDeviceState"},
- {21, &IUser::GetNpadId, "GetNpadId"},
- {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
- {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
- {24, nullptr, "RecreateApplicationArea"},
+ {0, &IUserManager::CreateUserInterface, "CreateUserInterface"},
};
- RegisterHandlers(functions);
-
- deactivate_event = service_context.CreateEvent("NFP::IUser:DeactivateEvent");
- availability_change_event =
- service_context.CreateEvent("NFP::IUser:AvailabilityChangeEvent");
- }
+ // clang-format on
- ~IUser() override {
- service_context.CloseEvent(deactivate_event);
- service_context.CloseEvent(availability_change_event);
+ RegisterHandlers(functions);
}
private:
- struct TagInfo {
- std::array<u8, 10> uuid;
- u8 uuid_length; // TODO(ogniK): Figure out if this is actual the uuid length or does it
- // mean something else
- std::array<u8, 0x15> padding_1;
- u32_le protocol;
- u32_le tag_type;
- std::array<u8, 0x2c> padding_2;
- };
- static_assert(sizeof(TagInfo) == 0x54, "TagInfo is an invalid size");
-
- enum class State : u32 {
- NonInitialized = 0,
- Initialized = 1,
- };
-
- enum class DeviceState : u32 {
- Initialized = 0,
- SearchingForTag = 1,
- TagFound = 2,
- TagRemoved = 3,
- TagNearby = 4,
- Unknown5 = 5,
- Finalized = 6
- };
-
- struct CommonInfo {
- u16_be last_write_year;
- u8 last_write_month;
- u8 last_write_day;
- u16_be write_counter;
- u16_be version;
- u32_be application_area_size;
- INSERT_PADDING_BYTES(0x34);
- };
- static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
-
- void Initialize(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFC, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0};
- rb.Push(ResultSuccess);
-
- state = State::Initialized;
- }
-
- void GetState(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFC, "called");
-
- IPC::ResponseBuilder rb{ctx, 3, 0};
- rb.Push(ResultSuccess);
- rb.PushRaw<u32>(static_cast<u32>(state));
- }
-
- void ListDevices(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u32 array_size = rp.Pop<u32>();
- LOG_DEBUG(Service_NFP, "called, array_size={}", array_size);
-
- ctx.WriteBuffer(device_handle);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(1);
- }
-
- void GetNpadId(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 dev_handle = rp.Pop<u64>();
- LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(npad_id);
- }
-
- void AttachActivateEvent(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 dev_handle = rp.Pop<u64>();
- LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle);
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(nfp_interface.GetNFCEvent());
- has_attached_handle = true;
- }
-
- void AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 dev_handle = rp.Pop<u64>();
- LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle);
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(deactivate_event->GetReadableEvent());
- }
-
- void StopDetection(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "called");
-
- switch (device_state) {
- case DeviceState::TagFound:
- case DeviceState::TagNearby:
- deactivate_event->GetWritableEvent().Signal();
- device_state = DeviceState::Initialized;
- break;
- case DeviceState::SearchingForTag:
- case DeviceState::TagRemoved:
- device_state = DeviceState::Initialized;
- break;
- default:
- break;
- }
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void GetDeviceState(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(device_state));
- }
-
- void StartDetection(Kernel::HLERequestContext& ctx) {
+ void CreateUserInterface(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NFP, "called");
- if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
- device_state = DeviceState::SearchingForTag;
+ if (user_interface == nullptr) {
+ user_interface = std::make_shared<IUser>(system);
}
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void GetTagInfo(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- const auto& amiibo = nfp_interface.GetAmiiboBuffer();
- const TagInfo tag_info{
- .uuid = amiibo.uuid,
- .uuid_length = static_cast<u8>(amiibo.uuid.size()),
- .padding_1 = {},
- .protocol = 1, // TODO(ogniK): Figure out actual values
- .tag_type = 2,
- .padding_2 = {},
- };
- ctx.WriteBuffer(tag_info);
- rb.Push(ResultSuccess);
- }
-
- void Mount(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "called");
-
- device_state = DeviceState::TagNearby;
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void GetModelInfo(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- const auto& amiibo = nfp_interface.GetAmiiboBuffer();
- ctx.WriteBuffer(amiibo.model_info);
- rb.Push(ResultSuccess);
- }
-
- void Unmount(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "called");
-
- device_state = DeviceState::TagFound;
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void Finalize(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "called");
-
- device_state = DeviceState::Finalized;
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NFP, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(availability_change_event->GetReadableEvent());
- }
-
- void GetRegisterInfo(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NFP, "(STUBBED) called");
-
- // TODO(ogniK): Pull Mii and owner data from amiibo
- IPC::ResponseBuilder rb{ctx, 2};
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IUser>(user_interface);
}
- void GetCommonInfo(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NFP, "(STUBBED) called");
-
- // TODO(ogniK): Pull common information from amiibo
-
- CommonInfo common_info{};
- common_info.application_area_size = 0;
- ctx.WriteBuffer(common_info);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void OpenApplicationArea(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NFP, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::ERR_NO_APPLICATION_AREA);
- }
-
- void GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NFP, "(STUBBED) called");
- // We don't need to worry about this since we can just open the file
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushRaw<u32>(0); // This is from the GetCommonInfo stub
- }
-
- void GetApplicationArea(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NFP, "(STUBBED) called");
-
- // TODO(ogniK): Pull application area from amiibo
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushRaw<u32>(0); // This is from the GetCommonInfo stub
- }
-
- Module::Interface& nfp_interface;
- KernelHelpers::ServiceContext& service_context;
-
- bool has_attached_handle{};
- const u64 device_handle{0}; // Npad device 1
- const u32 npad_id{0}; // Player 1 controller
- State state{State::NonInitialized};
- DeviceState device_state{DeviceState::Initialized};
- Kernel::KEvent* deactivate_event;
- Kernel::KEvent* availability_change_event;
+ std::shared_ptr<IUser> user_interface;
};
-void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IUser>(*this, system, service_context);
-}
-
-bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
- if (buffer.size() < sizeof(AmiiboFile)) {
- return false;
- }
-
- std::memcpy(&amiibo, buffer.data(), sizeof(amiibo));
- nfc_tag_load->GetWritableEvent().Signal();
- return true;
-}
-
-Kernel::KReadableEvent& Module::Interface::GetNFCEvent() {
- return nfc_tag_load->GetReadableEvent();
-}
-
-const Module::Interface::AmiiboFile& Module::Interface::GetAmiiboBuffer() const {
- return amiibo;
-}
-
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
- auto module = std::make_shared<Module>();
- std::make_shared<NFP_User>(module, system)->InstallAsService(service_manager);
+ std::make_shared<IUserManager>(system)->InstallAsService(service_manager);
}
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h
index 95c127efb..a25c362b8 100644
--- a/src/core/hle/service/nfp/nfp.h
+++ b/src/core/hle/service/nfp/nfp.h
@@ -1,57 +1,12 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <array>
-#include <vector>
-
-#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/service.h"
-namespace Kernel {
-class KEvent;
-}
-
namespace Service::NFP {
-class Module final {
-public:
- class Interface : public ServiceFramework<Interface> {
- public:
- explicit Interface(std::shared_ptr<Module> module_, Core::System& system_,
- const char* name);
- ~Interface() override;
-
- struct ModelInfo {
- std::array<u8, 0x8> amiibo_identification_block;
- INSERT_PADDING_BYTES(0x38);
- };
- static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
-
- struct AmiiboFile {
- std::array<u8, 10> uuid;
- INSERT_PADDING_BYTES(0x4a);
- ModelInfo model_info;
- };
- static_assert(sizeof(AmiiboFile) == 0x94, "AmiiboFile is an invalid size");
-
- void CreateUserInterface(Kernel::HLERequestContext& ctx);
- bool LoadAmiibo(const std::vector<u8>& buffer);
- Kernel::KReadableEvent& GetNFCEvent();
- const AmiiboFile& GetAmiiboBuffer() const;
-
- protected:
- std::shared_ptr<Module> module;
-
- private:
- KernelHelpers::ServiceContext service_context;
- Kernel::KEvent* nfc_tag_load;
- AmiiboFile amiibo{};
- };
-};
-
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp
new file mode 100644
index 000000000..ec895ac01
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_device.cpp
@@ -0,0 +1,681 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <atomic>
+
+#include "common/fs/file.h"
+#include "common/fs/path_util.h"
+#include "common/input.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "common/tiny_mt.h"
+#include "core/core.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#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/amiibo_crypto.h"
+#include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/nfp/nfp_device.h"
+#include "core/hle/service/nfp/nfp_result.h"
+#include "core/hle/service/nfp/nfp_user.h"
+#include "core/hle/service/time/time_manager.h"
+#include "core/hle/service/time/time_zone_content_manager.h"
+#include "core/hle/service/time/time_zone_types.h"
+
+namespace Service::NFP {
+NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
+ KernelHelpers::ServiceContext& service_context_,
+ Kernel::KEvent* availability_change_event_)
+ : npad_id{npad_id_}, system{system_}, service_context{service_context_},
+ availability_change_event{availability_change_event_} {
+ activate_event = service_context.CreateEvent("IUser:NFPActivateEvent");
+ deactivate_event = service_context.CreateEvent("IUser:NFPDeactivateEvent");
+ npad_device = system.HIDCore().GetEmulatedController(npad_id);
+
+ Core::HID::ControllerUpdateCallback engine_callback{
+ .on_change = [this](Core::HID::ControllerTriggerType type) { NpadUpdate(type); },
+ .is_npad_service = false,
+ };
+ is_controller_set = true;
+ callback_key = npad_device->SetCallback(engine_callback);
+
+ auto& standard_steady_clock{system.GetTimeManager().GetStandardSteadyClockCore()};
+ current_posix_time = standard_steady_clock.GetCurrentTimePoint(system).time_point;
+}
+
+NfpDevice::~NfpDevice() {
+ if (!is_controller_set) {
+ return;
+ }
+ npad_device->DeleteCallback(callback_key);
+ is_controller_set = false;
+};
+
+void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
+ if (type == Core::HID::ControllerTriggerType::Connected ||
+ type == Core::HID::ControllerTriggerType::Disconnected) {
+ availability_change_event->GetWritableEvent().Signal();
+ return;
+ }
+
+ if (type != Core::HID::ControllerTriggerType::Nfc) {
+ return;
+ }
+
+ if (!npad_device->IsConnected()) {
+ return;
+ }
+
+ const auto nfc_status = npad_device->GetNfc();
+ switch (nfc_status.state) {
+ case Common::Input::NfcState::NewAmiibo:
+ LoadAmiibo(nfc_status.data);
+ break;
+ case Common::Input::NfcState::AmiiboRemoved:
+ if (device_state != DeviceState::SearchingForTag) {
+ CloseAmiibo();
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+bool NfpDevice::LoadAmiibo(std::span<const u8> data) {
+ if (device_state != DeviceState::SearchingForTag) {
+ LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
+ return false;
+ }
+
+ if (data.size() != sizeof(EncryptedNTAG215File)) {
+ LOG_ERROR(Service_NFP, "Not an amiibo, size={}", data.size());
+ return false;
+ }
+
+ memcpy(&encrypted_tag_data, data.data(), sizeof(EncryptedNTAG215File));
+
+ device_state = DeviceState::TagFound;
+ deactivate_event->GetReadableEvent().Clear();
+ activate_event->GetWritableEvent().Signal();
+ return true;
+}
+
+void NfpDevice::CloseAmiibo() {
+ LOG_INFO(Service_NFP, "Remove amiibo");
+
+ if (device_state == DeviceState::TagMounted) {
+ Unmount();
+ }
+
+ device_state = DeviceState::TagRemoved;
+ encrypted_tag_data = {};
+ tag_data = {};
+ activate_event->GetReadableEvent().Clear();
+ deactivate_event->GetWritableEvent().Signal();
+}
+
+Kernel::KReadableEvent& NfpDevice::GetActivateEvent() const {
+ return activate_event->GetReadableEvent();
+}
+
+Kernel::KReadableEvent& NfpDevice::GetDeactivateEvent() const {
+ return deactivate_event->GetReadableEvent();
+}
+
+void NfpDevice::Initialize() {
+ device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable;
+ encrypted_tag_data = {};
+ tag_data = {};
+}
+
+void NfpDevice::Finalize() {
+ if (device_state == DeviceState::TagMounted) {
+ Unmount();
+ }
+ if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
+ StopDetection();
+ }
+ device_state = DeviceState::Unavailable;
+}
+
+Result NfpDevice::StartDetection(s32 protocol_) {
+ if (device_state != DeviceState::Initialized && device_state != DeviceState::TagRemoved) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ return WrongDeviceState;
+ }
+
+ if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) {
+ LOG_ERROR(Service_NFP, "Nfc not supported");
+ return NfcDisabled;
+ }
+
+ device_state = DeviceState::SearchingForTag;
+ protocol = protocol_;
+ return ResultSuccess;
+}
+
+Result NfpDevice::StopDetection() {
+ npad_device->SetPollingMode(Common::Input::PollingMode::Active);
+
+ if (device_state == DeviceState::Initialized) {
+ return ResultSuccess;
+ }
+
+ if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
+ CloseAmiibo();
+ return ResultSuccess;
+ }
+ if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
+ device_state = DeviceState::Initialized;
+ return ResultSuccess;
+ }
+
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ return WrongDeviceState;
+}
+
+Result NfpDevice::Flush() {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ auto& settings = tag_data.settings;
+
+ const auto& current_date = GetAmiiboDate(current_posix_time);
+ if (settings.write_date.raw_date != current_date.raw_date) {
+ settings.write_date = current_date;
+ settings.crc_counter++;
+ // TODO: Find how to calculate the crc check
+ // settings.crc = CalculateCRC(settings);
+ }
+
+ tag_data.write_counter++;
+
+ if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
+ LOG_ERROR(Service_NFP, "Failed to encode data");
+ return WriteAmiiboFailed;
+ }
+
+ std::vector<u8> data(sizeof(encrypted_tag_data));
+ memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
+
+ if (!npad_device->WriteNfc(data)) {
+ LOG_ERROR(Service_NFP, "Error writing to file");
+ return WriteAmiiboFailed;
+ }
+
+ is_data_moddified = false;
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::Mount(MountTarget mount_target_) {
+ if (device_state != DeviceState::TagFound) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ return WrongDeviceState;
+ }
+
+ if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
+ LOG_ERROR(Service_NFP, "Not an amiibo");
+ return NotAnAmiibo;
+ }
+
+ if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
+ LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state);
+ return CorruptedData;
+ }
+
+ device_state = DeviceState::TagMounted;
+ mount_target = mount_target_;
+ return ResultSuccess;
+}
+
+Result NfpDevice::Unmount() {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ // Save data before unloading the amiibo
+ if (is_data_moddified) {
+ Flush();
+ }
+
+ device_state = DeviceState::TagFound;
+ mount_target = MountTarget::None;
+ is_app_area_open = false;
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetTagInfo(TagInfo& tag_info) const {
+ if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ tag_info = {
+ .uuid = encrypted_tag_data.uuid.uid,
+ .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
+ .protocol = TagProtocol::TypeA,
+ .tag_type = TagType::Type2,
+ };
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ const auto& settings = tag_data.settings;
+
+ // TODO: Validate this data
+ common_info = {
+ .last_write_date = settings.write_date.GetWriteDate(),
+ .write_counter = tag_data.write_counter,
+ .version = 0,
+ .application_area_size = sizeof(ApplicationArea),
+ };
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetModelInfo(ModelInfo& model_info) const {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ const auto& model_info_data = encrypted_tag_data.user_memory.model_info;
+ model_info = {
+ .character_id = model_info_data.character_id,
+ .character_variant = model_info_data.character_variant,
+ .amiibo_type = model_info_data.amiibo_type,
+ .model_number = model_info_data.model_number,
+ .series = model_info_data.series,
+ };
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.amiibo_initialized == 0) {
+ return RegistrationIsNotInitialized;
+ }
+
+ Service::Mii::MiiManager manager;
+ const auto& settings = tag_data.settings;
+
+ // TODO: Validate this data
+ register_info = {
+ .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
+ .creation_date = settings.init_date.GetWriteDate(),
+ .amiibo_name = GetAmiiboName(settings),
+ .font_region = {},
+ };
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::SetNicknameAndOwner(const AmiiboName& amiibo_name) {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ Service::Mii::MiiManager manager;
+ auto& settings = tag_data.settings;
+
+ settings.init_date = GetAmiiboDate(current_posix_time);
+ settings.write_date = GetAmiiboDate(current_posix_time);
+ settings.crc_counter++;
+ // TODO: Find how to calculate the crc check
+ // settings.crc = CalculateCRC(settings);
+
+ SetAmiiboName(settings, amiibo_name);
+ tag_data.owner_mii = manager.ConvertCharInfoToV3(manager.BuildDefault(0));
+ settings.settings.amiibo_initialized.Assign(1);
+
+ return Flush();
+}
+
+Result NfpDevice::RestoreAmiibo() {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ // TODO: Load amiibo from backup on system
+ LOG_ERROR(Service_NFP, "Not Implemented");
+ return ResultSuccess;
+}
+
+Result NfpDevice::DeleteAllData() {
+ const auto result = DeleteApplicationArea();
+ if (result.IsError()) {
+ return result;
+ }
+
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ Common::TinyMT rng{};
+ rng.GenerateRandomBytes(&tag_data.owner_mii, sizeof(tag_data.owner_mii));
+ tag_data.settings.settings.amiibo_initialized.Assign(0);
+
+ return Flush();
+}
+
+Result NfpDevice::OpenApplicationArea(u32 access_id) {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
+ LOG_WARNING(Service_NFP, "Application area is not initialized");
+ return ApplicationAreaIsNotInitialized;
+ }
+
+ if (tag_data.application_area_id != access_id) {
+ LOG_WARNING(Service_NFP, "Wrong application area id");
+ return WrongApplicationAreaId;
+ }
+
+ is_app_area_open = true;
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (!is_app_area_open) {
+ LOG_ERROR(Service_NFP, "Application area is not open");
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
+ LOG_ERROR(Service_NFP, "Application area is not initialized");
+ return ApplicationAreaIsNotInitialized;
+ }
+
+ if (data.size() > sizeof(ApplicationArea)) {
+ data.resize(sizeof(ApplicationArea));
+ }
+
+ memcpy(data.data(), tag_data.application_area.data(), data.size());
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::SetApplicationArea(std::span<const u8> data) {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (!is_app_area_open) {
+ LOG_ERROR(Service_NFP, "Application area is not open");
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
+ LOG_ERROR(Service_NFP, "Application area is not initialized");
+ return ApplicationAreaIsNotInitialized;
+ }
+
+ if (data.size() > sizeof(ApplicationArea)) {
+ LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
+ return ResultUnknown;
+ }
+
+ Common::TinyMT rng{};
+ std::memcpy(tag_data.application_area.data(), data.data(), data.size());
+ // Fill remaining data with random numbers
+ rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
+ sizeof(ApplicationArea) - data.size());
+
+ tag_data.applicaton_write_counter++;
+ is_data_moddified = true;
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::CreateApplicationArea(u32 access_id, std::span<const u8> data) {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.appdata_initialized.Value() != 0) {
+ LOG_ERROR(Service_NFP, "Application area already exist");
+ return ApplicationAreaExist;
+ }
+
+ return RecreateApplicationArea(access_id, data);
+}
+
+Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> data) {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (data.size() > sizeof(ApplicationArea)) {
+ LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
+ return WrongApplicationAreaSize;
+ }
+
+ Common::TinyMT rng{};
+ std::memcpy(tag_data.application_area.data(), data.data(), data.size());
+ // Fill remaining data with random numbers
+ rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
+ sizeof(ApplicationArea) - data.size());
+
+ // TODO: Investigate why the title id needs to be moddified
+ tag_data.title_id = system.GetCurrentProcessProgramID();
+ tag_data.title_id = tag_data.title_id | 0x30000000ULL;
+ tag_data.settings.settings.appdata_initialized.Assign(1);
+ tag_data.application_area_id = access_id;
+ tag_data.applicaton_write_counter++;
+ tag_data.unknown = {};
+
+ return Flush();
+}
+
+Result NfpDevice::DeleteApplicationArea() {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ Common::TinyMT rng{};
+ rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(ApplicationArea));
+ rng.GenerateRandomBytes(&tag_data.title_id, sizeof(u64));
+ rng.GenerateRandomBytes(&tag_data.application_area_id, sizeof(u32));
+ tag_data.settings.settings.appdata_initialized.Assign(0);
+ tag_data.applicaton_write_counter++;
+ tag_data.unknown = {};
+
+ return Flush();
+}
+
+u64 NfpDevice::GetHandle() const {
+ // Generate a handle based of the npad id
+ return static_cast<u64>(npad_id);
+}
+
+u32 NfpDevice::GetApplicationAreaSize() const {
+ return sizeof(ApplicationArea);
+}
+
+DeviceState NfpDevice::GetCurrentState() const {
+ return device_state;
+}
+
+Core::HID::NpadIdType NfpDevice::GetNpadId() const {
+ return npad_id;
+}
+
+AmiiboName NfpDevice::GetAmiiboName(const AmiiboSettings& settings) const {
+ std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
+ AmiiboName amiibo_name{};
+
+ // Convert from big endian to little endian
+ for (std::size_t i = 0; i < amiibo_name_length; i++) {
+ settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]);
+ }
+
+ // Convert from utf16 to utf8
+ const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
+ memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
+
+ return amiibo_name;
+}
+
+void NfpDevice::SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name) {
+ std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
+
+ // Convert from utf8 to utf16
+ const auto amiibo_name_utf16 = Common::UTF8ToUTF16(amiibo_name.data());
+ memcpy(settings_amiibo_name.data(), amiibo_name_utf16.data(),
+ amiibo_name_utf16.size() * sizeof(char16_t));
+
+ // Convert from little endian to big endian
+ for (std::size_t i = 0; i < amiibo_name_length; i++) {
+ settings.amiibo_name[i] = static_cast<u16_be>(settings_amiibo_name[i]);
+ }
+}
+
+AmiiboDate NfpDevice::GetAmiiboDate(s64 posix_time) const {
+ const auto& time_zone_manager =
+ system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
+ Time::TimeZone::CalendarInfo calendar_info{};
+ AmiiboDate amiibo_date{};
+
+ amiibo_date.SetYear(2000);
+ amiibo_date.SetMonth(1);
+ amiibo_date.SetDay(1);
+
+ if (time_zone_manager.ToCalendarTime({}, posix_time, calendar_info) == ResultSuccess) {
+ amiibo_date.SetYear(calendar_info.time.year);
+ amiibo_date.SetMonth(calendar_info.time.month);
+ amiibo_date.SetDay(calendar_info.time.day);
+ }
+
+ return amiibo_date;
+}
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h
new file mode 100644
index 000000000..a5b72cf19
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_device.h
@@ -0,0 +1,101 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/mii/types.h"
+#include "core/hle/service/nfp/nfp_types.h"
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace Core::HID {
+class EmulatedController;
+enum class ControllerTriggerType;
+enum class NpadIdType : u32;
+} // namespace Core::HID
+
+namespace Service::NFP {
+class NfpDevice {
+public:
+ NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
+ KernelHelpers::ServiceContext& service_context_,
+ Kernel::KEvent* availability_change_event_);
+ ~NfpDevice();
+
+ void Initialize();
+ void Finalize();
+
+ Result StartDetection(s32 protocol_);
+ Result StopDetection();
+ Result Mount(MountTarget mount_target);
+ Result Unmount();
+ Result Flush();
+
+ Result GetTagInfo(TagInfo& tag_info) const;
+ Result GetCommonInfo(CommonInfo& common_info) const;
+ Result GetModelInfo(ModelInfo& model_info) const;
+ Result GetRegisterInfo(RegisterInfo& register_info) const;
+
+ Result SetNicknameAndOwner(const AmiiboName& amiibo_name);
+ Result RestoreAmiibo();
+ Result DeleteAllData();
+
+ Result OpenApplicationArea(u32 access_id);
+ Result GetApplicationArea(std::vector<u8>& data) const;
+ Result SetApplicationArea(std::span<const u8> data);
+ Result CreateApplicationArea(u32 access_id, std::span<const u8> data);
+ Result RecreateApplicationArea(u32 access_id, std::span<const u8> data);
+ Result DeleteApplicationArea();
+
+ u64 GetHandle() const;
+ u32 GetApplicationAreaSize() const;
+ DeviceState GetCurrentState() const;
+ Core::HID::NpadIdType GetNpadId() const;
+
+ Kernel::KReadableEvent& GetActivateEvent() const;
+ Kernel::KReadableEvent& GetDeactivateEvent() const;
+
+private:
+ void NpadUpdate(Core::HID::ControllerTriggerType type);
+ bool LoadAmiibo(std::span<const u8> data);
+ void CloseAmiibo();
+
+ AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
+ void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name);
+ AmiiboDate GetAmiiboDate(s64 posix_time) const;
+
+ bool is_controller_set{};
+ int callback_key;
+ const Core::HID::NpadIdType npad_id;
+ Core::System& system;
+ Core::HID::EmulatedController* npad_device = nullptr;
+ KernelHelpers::ServiceContext& service_context;
+ Kernel::KEvent* activate_event = nullptr;
+ Kernel::KEvent* deactivate_event = nullptr;
+ Kernel::KEvent* availability_change_event = nullptr;
+
+ bool is_data_moddified{};
+ bool is_app_area_open{};
+ s32 protocol{};
+ s64 current_posix_time{};
+ MountTarget mount_target{MountTarget::None};
+ DeviceState device_state{DeviceState::Unavailable};
+
+ NTAG215File tag_data{};
+ EncryptedNTAG215File encrypted_tag_data{};
+};
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_result.h b/src/core/hle/service/nfp/nfp_result.h
new file mode 100644
index 000000000..d8e4cf094
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_result.h
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::NFP {
+
+constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
+constexpr Result InvalidArgument(ErrorModule::NFP, 65);
+constexpr Result WrongApplicationAreaSize(ErrorModule::NFP, 68);
+constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
+constexpr Result NfcDisabled(ErrorModule::NFP, 80);
+constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88);
+constexpr Result TagRemoved(ErrorModule::NFP, 97);
+constexpr Result RegistrationIsNotInitialized(ErrorModule::NFP, 120);
+constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
+constexpr Result CorruptedData(ErrorModule::NFP, 144);
+constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152);
+constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
+constexpr Result NotAnAmiibo(ErrorModule::NFP, 178);
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h
new file mode 100644
index 000000000..867ea2f36
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -0,0 +1,320 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "common/swap.h"
+#include "core/hle/service/mii/types.h"
+
+namespace Service::NFP {
+static constexpr std::size_t amiibo_name_length = 0xA;
+
+enum class ServiceType : u32 {
+ User,
+ Debug,
+ System,
+};
+
+enum class State : u32 {
+ NonInitialized,
+ Initialized,
+};
+
+enum class DeviceState : u32 {
+ Initialized,
+ SearchingForTag,
+ TagFound,
+ TagRemoved,
+ TagMounted,
+ Unavailable,
+ Finalized,
+};
+
+enum class ModelType : u32 {
+ Amiibo,
+};
+
+enum class MountTarget : u32 {
+ None,
+ Rom,
+ Ram,
+ All,
+};
+
+enum class AmiiboType : u8 {
+ Figure,
+ Card,
+ Yarn,
+};
+
+enum class AmiiboSeries : u8 {
+ SuperSmashBros,
+ SuperMario,
+ ChibiRobo,
+ YoshiWoollyWorld,
+ Splatoon,
+ AnimalCrossing,
+ EightBitMario,
+ Skylanders,
+ Unknown8,
+ TheLegendOfZelda,
+ ShovelKnight,
+ Unknown11,
+ Kiby,
+ Pokemon,
+ MarioSportsSuperstars,
+ MonsterHunter,
+ BoxBoy,
+ Pikmin,
+ FireEmblem,
+ Metroid,
+ Others,
+ MegaMan,
+ Diablo,
+};
+
+enum class TagType : u32 {
+ None,
+ Type1, // ISO14443A RW 96-2k bytes 106kbit/s
+ Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
+ Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
+ Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
+ Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
+};
+
+enum class PackedTagType : u8 {
+ None,
+ Type1, // ISO14443A RW 96-2k bytes 106kbit/s
+ Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
+ Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
+ Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
+ Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
+};
+
+enum class TagProtocol : u32 {
+ None,
+ TypeA, // ISO14443A
+ TypeB, // ISO14443B
+ TypeF, // Sony Felica
+};
+
+using UniqueSerialNumber = std::array<u8, 7>;
+using LockBytes = std::array<u8, 2>;
+using HashData = std::array<u8, 0x20>;
+using ApplicationArea = std::array<u8, 0xD8>;
+using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
+
+struct TagUuid {
+ UniqueSerialNumber uid;
+ u8 nintendo_id;
+ LockBytes lock_bytes;
+};
+static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size");
+
+struct WriteDate {
+ u16 year;
+ u8 month;
+ u8 day;
+};
+static_assert(sizeof(WriteDate) == 0x4, "WriteDate is an invalid size");
+
+struct AmiiboDate {
+ u16 raw_date{};
+
+ u16 GetValue() const {
+ return Common::swap16(raw_date);
+ }
+
+ u16 GetYear() const {
+ return static_cast<u16>(((GetValue() & 0xFE00) >> 9) + 2000);
+ }
+ u8 GetMonth() const {
+ return static_cast<u8>((GetValue() & 0x01E0) >> 5);
+ }
+ u8 GetDay() const {
+ return static_cast<u8>(GetValue() & 0x001F);
+ }
+
+ WriteDate GetWriteDate() const {
+ if (!IsValidDate()) {
+ return {
+ .year = 2000,
+ .month = 1,
+ .day = 1,
+ };
+ }
+ return {
+ .year = GetYear(),
+ .month = GetMonth(),
+ .day = GetDay(),
+ };
+ }
+
+ void SetYear(u16 year) {
+ const u16 year_converted = static_cast<u16>((year - 2000) << 9);
+ raw_date = Common::swap16((GetValue() & ~0xFE00) | year_converted);
+ }
+ void SetMonth(u8 month) {
+ const u16 month_converted = static_cast<u16>(month << 5);
+ raw_date = Common::swap16((GetValue() & ~0x01E0) | month_converted);
+ }
+ void SetDay(u8 day) {
+ const u16 day_converted = static_cast<u16>(day);
+ raw_date = Common::swap16((GetValue() & ~0x001F) | day_converted);
+ }
+
+ bool IsValidDate() const {
+ const bool is_day_valid = GetDay() > 0 && GetDay() < 32;
+ const bool is_month_valid = GetMonth() >= 0 && GetMonth() < 13;
+ const bool is_year_valid = GetYear() >= 2000;
+ return is_year_valid && is_month_valid && is_day_valid;
+ }
+};
+static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
+
+struct Settings {
+ union {
+ u8 raw{};
+
+ BitField<4, 1, u8> amiibo_initialized;
+ BitField<5, 1, u8> appdata_initialized;
+ };
+};
+static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size");
+
+struct AmiiboSettings {
+ Settings settings;
+ u8 country_code_id;
+ u16_be crc_counter; // Incremented each time crc is changed
+ AmiiboDate init_date;
+ AmiiboDate write_date;
+ u32_be crc;
+ std::array<u16_be, amiibo_name_length> amiibo_name; // UTF-16 text
+};
+static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size");
+
+struct AmiiboModelInfo {
+ u16 character_id;
+ u8 character_variant;
+ AmiiboType amiibo_type;
+ u16_be model_number;
+ AmiiboSeries series;
+ PackedTagType tag_type;
+ INSERT_PADDING_BYTES(0x4); // Unknown
+};
+static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size");
+
+struct NTAG215Password {
+ u32 PWD; // Password to allow write access
+ u16 PACK; // Password acknowledge reply
+ u16 RFUI; // Reserved for future use
+};
+static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size");
+
+#pragma pack(1)
+struct EncryptedAmiiboFile {
+ u8 constant_value; // Must be A5
+ u16_be write_counter; // Number of times the amiibo has been written?
+ INSERT_PADDING_BYTES(0x1); // Unknown 1
+ AmiiboSettings settings; // Encrypted amiibo settings
+ HashData hmac_tag; // Hash
+ AmiiboModelInfo model_info; // Encrypted amiibo model info
+ HashData keygen_salt; // Salt
+ HashData hmac_data; // Hash
+ Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
+ u64_be title_id; // Encrypted Game id
+ u16_be applicaton_write_counter; // Encrypted Counter
+ u32_be application_area_id; // Encrypted Game id
+ std::array<u8, 0x2> unknown;
+ std::array<u32, 0x8> unknown2;
+ ApplicationArea application_area; // Encrypted Game data
+};
+static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
+
+struct NTAG215File {
+ LockBytes lock_bytes; // Tag UUID
+ u16 static_lock; // Set defined pages as read only
+ u32 compability_container; // Defines available memory
+ HashData hmac_data; // Hash
+ u8 constant_value; // Must be A5
+ u16_be write_counter; // Number of times the amiibo has been written?
+ INSERT_PADDING_BYTES(0x1); // Unknown 1
+ AmiiboSettings settings;
+ Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
+ u64_be title_id;
+ u16_be applicaton_write_counter; // Encrypted Counter
+ u32_be application_area_id;
+ std::array<u8, 0x2> unknown;
+ std::array<u32, 0x8> unknown2;
+ ApplicationArea application_area; // Encrypted Game data
+ HashData hmac_tag; // Hash
+ UniqueSerialNumber uid; // Unique serial number
+ u8 nintendo_id; // Tag UUID
+ AmiiboModelInfo model_info;
+ HashData keygen_salt; // Salt
+ u32 dynamic_lock; // Dynamic lock
+ u32 CFG0; // Defines memory protected by password
+ u32 CFG1; // Defines number of verification attempts
+ NTAG215Password password; // Password data
+};
+static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size");
+static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be trivially copyable.");
+#pragma pack()
+
+struct EncryptedNTAG215File {
+ TagUuid uuid; // Unique serial number
+ u16 static_lock; // Set defined pages as read only
+ u32 compability_container; // Defines available memory
+ EncryptedAmiiboFile user_memory; // Writable data
+ u32 dynamic_lock; // Dynamic lock
+ u32 CFG0; // Defines memory protected by password
+ u32 CFG1; // Defines number of verification attempts
+ NTAG215Password password; // Password data
+};
+static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size");
+static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
+ "EncryptedNTAG215File must be trivially copyable.");
+
+struct TagInfo {
+ UniqueSerialNumber uuid;
+ INSERT_PADDING_BYTES(0x3);
+ u8 uuid_length;
+ INSERT_PADDING_BYTES(0x15);
+ TagProtocol protocol;
+ TagType tag_type;
+ INSERT_PADDING_BYTES(0x30);
+};
+static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
+
+struct CommonInfo {
+ WriteDate last_write_date;
+ u16 write_counter;
+ u8 version;
+ INSERT_PADDING_BYTES(0x1);
+ u32 application_area_size;
+ INSERT_PADDING_BYTES(0x34);
+};
+static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
+
+struct ModelInfo {
+ u16 character_id;
+ u8 character_variant;
+ AmiiboType amiibo_type;
+ u16 model_number;
+ AmiiboSeries series;
+ INSERT_PADDING_BYTES(0x39); // Unknown
+};
+static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
+
+struct RegisterInfo {
+ Service::Mii::CharInfo mii_char_info;
+ WriteDate creation_date;
+ AmiiboName amiibo_name;
+ u8 font_region;
+ INSERT_PADDING_BYTES(0x7A);
+};
+static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp
index 10b0ef944..4ed53b534 100644
--- a/src/core/hle/service/nfp/nfp_user.cpp
+++ b/src/core/hle/service/nfp/nfp_user.cpp
@@ -1,19 +1,674 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <array>
+#include <atomic>
+
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#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_device.h"
+#include "core/hle/service/nfp/nfp_result.h"
#include "core/hle/service/nfp/nfp_user.h"
namespace Service::NFP {
-NFP_User::NFP_User(std::shared_ptr<Module> module_, Core::System& system_)
- : Interface(std::move(module_), system_, "nfp:user") {
+IUser::IUser(Core::System& system_)
+ : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name} {
static const FunctionInfo functions[] = {
- {0, &NFP_User::CreateUserInterface, "CreateUserInterface"},
+ {0, &IUser::Initialize, "Initialize"},
+ {1, &IUser::Finalize, "Finalize"},
+ {2, &IUser::ListDevices, "ListDevices"},
+ {3, &IUser::StartDetection, "StartDetection"},
+ {4, &IUser::StopDetection, "StopDetection"},
+ {5, &IUser::Mount, "Mount"},
+ {6, &IUser::Unmount, "Unmount"},
+ {7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
+ {8, &IUser::GetApplicationArea, "GetApplicationArea"},
+ {9, &IUser::SetApplicationArea, "SetApplicationArea"},
+ {10, &IUser::Flush, "Flush"},
+ {11, &IUser::Restore, "Restore"},
+ {12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
+ {13, &IUser::GetTagInfo, "GetTagInfo"},
+ {14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
+ {15, &IUser::GetCommonInfo, "GetCommonInfo"},
+ {16, &IUser::GetModelInfo, "GetModelInfo"},
+ {17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
+ {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
+ {19, &IUser::GetState, "GetState"},
+ {20, &IUser::GetDeviceState, "GetDeviceState"},
+ {21, &IUser::GetNpadId, "GetNpadId"},
+ {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
+ {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
+ {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
};
RegisterHandlers(functions);
+
+ availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
+
+ for (u32 device_index = 0; device_index < 10; device_index++) {
+ devices[device_index] =
+ std::make_shared<NfpDevice>(Core::HID::IndexToNpadIdType(device_index), system,
+ service_context, availability_change_event);
+ }
+}
+
+void IUser::Initialize(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_NFC, "called");
+
+ state = State::Initialized;
+
+ for (auto& device : devices) {
+ device->Initialize();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 0};
+ rb.Push(ResultSuccess);
+}
+
+void IUser::Finalize(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_NFP, "called");
+
+ state = State::NonInitialized;
+
+ for (auto& device : devices) {
+ device->Finalize();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_NFP, "called");
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ if (!ctx.CanWriteBuffer()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ if (ctx.GetWriteBufferSize() == 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ std::vector<u64> nfp_devices;
+ const std::size_t max_allowed_devices = ctx.GetWriteBufferSize() / sizeof(u64);
+
+ for (auto& device : devices) {
+ if (nfp_devices.size() >= max_allowed_devices) {
+ continue;
+ }
+ if (device->GetCurrentState() != DeviceState::Unavailable) {
+ nfp_devices.push_back(device->GetHandle());
+ }
+ }
+
+ if (nfp_devices.size() == 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ ctx.WriteBuffer(nfp_devices);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<s32>(nfp_devices.size()));
+}
+
+void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto nfp_protocol{rp.Pop<s32>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->StartDetection(nfp_protocol);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->StopDetection();
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::Mount(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto model_type{rp.PopEnum<ModelType>()};
+ const auto mount_target{rp.PopEnum<MountTarget>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle,
+ model_type, mount_target);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->Mount(mount_target);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::Unmount(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->Unmount();
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto access_id{rp.Pop<u32>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, access_id={}", device_handle, access_id);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->OpenApplicationArea(access_id);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto data_size = ctx.GetWriteBufferSize();
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ if (!ctx.CanWriteBuffer()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ std::vector<u8> data(data_size);
+ const auto result = device.value()->GetApplicationArea(data);
+ ctx.WriteBuffer(data);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(static_cast<u32>(data_size));
+}
+
+void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto data{ctx.ReadBuffer()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}", device_handle, data.size());
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ if (!ctx.CanReadBuffer()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->SetApplicationArea(data);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::Flush(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->Flush();
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::Restore(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->RestoreAmiibo();
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto access_id{rp.Pop<u32>()};
+ const auto data{ctx.ReadBuffer()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle,
+ access_id, data.size());
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ if (!ctx.CanReadBuffer()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->CreateApplicationArea(access_id, data);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ TagInfo tag_info{};
+ const auto result = device.value()->GetTagInfo(tag_info);
+ ctx.WriteBuffer(tag_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ RegisterInfo register_info{};
+ const auto result = device.value()->GetRegisterInfo(register_info);
+ ctx.WriteBuffer(register_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ CommonInfo common_info{};
+ const auto result = device.value()->GetCommonInfo(common_info);
+ ctx.WriteBuffer(common_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ ModelInfo model_info{};
+ const auto result = device.value()->GetModelInfo(model_info);
+ ctx.WriteBuffer(model_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(device.value()->GetActivateEvent());
+}
+
+void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(device.value()->GetDeactivateEvent());
+}
+
+void IUser::GetState(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFC, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3, 0};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(state);
+}
+
+void IUser::GetDeviceState(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(device.value()->GetCurrentState());
+}
+
+void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(device.value()->GetNpadId());
+}
+
+void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(device.value()->GetApplicationAreaSize());
}
-NFP_User::~NFP_User() = default;
+void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_NFP, "called");
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(availability_change_event->GetReadableEvent());
+}
+
+void IUser::RecreateApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto access_id{rp.Pop<u32>()};
+ const auto data{ctx.ReadBuffer()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle,
+ access_id, data.size());
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->RecreateApplicationArea(access_id, data);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+std::optional<std::shared_ptr<NfpDevice>> IUser::GetNfpDevice(u64 handle) {
+ for (auto& device : devices) {
+ if (device->GetHandle() == handle) {
+ return device;
+ }
+ }
+ return std::nullopt;
+}
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h
index 7f3c124f6..68c60ae82 100644
--- a/src/core/hle/service/nfp/nfp_user.h
+++ b/src/core/hle/service/nfp/nfp_user.h
@@ -1,17 +1,54 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/nfp/nfp_types.h"
namespace Service::NFP {
+class NfpDevice;
-class NFP_User final : public Module::Interface {
+class IUser final : public ServiceFramework<IUser> {
public:
- explicit NFP_User(std::shared_ptr<Module> module_, Core::System& system_);
- ~NFP_User() override;
+ explicit IUser(Core::System& system_);
+
+private:
+ void Initialize(Kernel::HLERequestContext& ctx);
+ void Finalize(Kernel::HLERequestContext& ctx);
+ void ListDevices(Kernel::HLERequestContext& ctx);
+ void StartDetection(Kernel::HLERequestContext& ctx);
+ void StopDetection(Kernel::HLERequestContext& ctx);
+ void Mount(Kernel::HLERequestContext& ctx);
+ void Unmount(Kernel::HLERequestContext& ctx);
+ void OpenApplicationArea(Kernel::HLERequestContext& ctx);
+ void GetApplicationArea(Kernel::HLERequestContext& ctx);
+ void SetApplicationArea(Kernel::HLERequestContext& ctx);
+ void Flush(Kernel::HLERequestContext& ctx);
+ void Restore(Kernel::HLERequestContext& ctx);
+ void CreateApplicationArea(Kernel::HLERequestContext& ctx);
+ void GetTagInfo(Kernel::HLERequestContext& ctx);
+ void GetRegisterInfo(Kernel::HLERequestContext& ctx);
+ void GetCommonInfo(Kernel::HLERequestContext& ctx);
+ void GetModelInfo(Kernel::HLERequestContext& ctx);
+ void AttachActivateEvent(Kernel::HLERequestContext& ctx);
+ void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
+ void GetState(Kernel::HLERequestContext& ctx);
+ void GetDeviceState(Kernel::HLERequestContext& ctx);
+ void GetNpadId(Kernel::HLERequestContext& ctx);
+ void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
+ void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
+ void RecreateApplicationArea(Kernel::HLERequestContext& ctx);
+
+ std::optional<std::shared_ptr<NfpDevice>> GetNfpDevice(u64 handle);
+
+ KernelHelpers::ServiceContext service_context;
+
+ std::array<std::shared_ptr<NfpDevice>, 10> devices{};
+
+ State state{State::NonInitialized};
+ Kernel::KEvent* availability_change_event;
};
} // namespace Service::NFP
diff --git a/src/core/hle/service/ngct/ngct.cpp b/src/core/hle/service/ngct/ngct.cpp
index 8ec7d5266..8af8a835d 100644
--- a/src/core/hle/service/ngct/ngct.cpp
+++ b/src/core/hle/service/ngct/ngct.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/string_util.h"
#include "core/core.h"
diff --git a/src/core/hle/service/ngct/ngct.h b/src/core/hle/service/ngct/ngct.h
index 1f2a47b78..370bd4a25 100644
--- a/src/core/hle/service/ngct/ngct.h
+++ b/src/core/hle/service/ngct/ngct.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index a253dd066..e3ef06481 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -1,14 +1,11 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_event.h"
-#include "core/hle/kernel/kernel.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nifm/nifm.h"
-#include "core/hle/service/service.h"
namespace {
@@ -20,8 +17,8 @@ namespace {
} // Anonymous namespace
-#include "core/network/network.h"
-#include "core/network/network_interface.h"
+#include "core/internal_network/network.h"
+#include "core/internal_network/network_interface.h"
namespace Service::NIFM {
@@ -32,6 +29,19 @@ enum class RequestState : u32 {
Connected = 3,
};
+enum class InternetConnectionType : u8 {
+ WiFi = 1,
+ Ethernet = 2,
+};
+
+enum class InternetConnectionStatus : u8 {
+ ConnectingUnknown1,
+ ConnectingUnknown2,
+ ConnectingUnknown3,
+ ConnectingUnknown4,
+ Connected,
+};
+
struct IpAddressSetting {
bool is_automatic{};
Network::IPv4Address current_address{};
@@ -260,135 +270,45 @@ public:
}
};
-class IGeneralService final : public ServiceFramework<IGeneralService> {
-public:
- explicit IGeneralService(Core::System& system_);
-
-private:
- void GetClientId(Kernel::HLERequestContext& ctx) {
- static constexpr u32 client_id = 1;
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
+void IGeneralService::GetClientId(Kernel::HLERequestContext& ctx) {
+ static constexpr u32 client_id = 1;
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.Push<u64>(client_id); // Client ID needs to be non zero otherwise it's considered invalid
- }
- void CreateScanRequest(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NIFM, "called");
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<u64>(client_id); // Client ID needs to be non zero otherwise it's considered invalid
+}
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+void IGeneralService::CreateScanRequest(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NIFM, "called");
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IScanRequest>(system);
- }
- void CreateRequest(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NIFM, "called");
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IScanRequest>(system);
+}
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IRequest>(system);
- }
- void GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
+void IGeneralService::CreateRequest(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NIFM, "called");
- const auto net_iface = Network::GetSelectedNetworkInterface();
-
- const SfNetworkProfileData network_profile_data = [&net_iface] {
- if (!net_iface) {
- return SfNetworkProfileData{};
- }
-
- return SfNetworkProfileData{
- .ip_setting_data{
- .ip_address_setting{
- .is_automatic{true},
- .current_address{Network::TranslateIPv4(net_iface->ip_address)},
- .subnet_mask{Network::TranslateIPv4(net_iface->subnet_mask)},
- .gateway{Network::TranslateIPv4(net_iface->gateway)},
- },
- .dns_setting{
- .is_automatic{true},
- .primary_dns{1, 1, 1, 1},
- .secondary_dns{1, 0, 0, 1},
- },
- .proxy_setting{
- .enabled{false},
- .port{},
- .proxy_server{},
- .automatic_auth_enabled{},
- .user{},
- .password{},
- },
- .mtu{1500},
- },
- .uuid{0xdeadbeef, 0xdeadbeef},
- .network_name{"yuzu Network"},
- .wireless_setting_data{
- .ssid_length{12},
- .ssid{"yuzu Network"},
- .passphrase{"yuzupassword"},
- },
- };
- }();
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- ctx.WriteBuffer(network_profile_data);
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IRequest>(system);
+}
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
- void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
+void IGeneralService::GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
- void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
+ const auto net_iface = Network::GetSelectedNetworkInterface();
- auto ipv4 = Network::GetHostIPv4Address();
- if (!ipv4) {
- LOG_ERROR(Service_NIFM, "Couldn't get host IPv4 address, defaulting to 0.0.0.0");
- ipv4.emplace(Network::IPv4Address{0, 0, 0, 0});
+ SfNetworkProfileData network_profile_data = [&net_iface] {
+ if (!net_iface) {
+ return SfNetworkProfileData{};
}
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushRaw(*ipv4);
- }
- void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NIFM, "called");
-
- ASSERT_MSG(ctx.GetReadBufferSize() == 0x17c,
- "SfNetworkProfileData is not the correct size");
- u128 uuid{};
- auto buffer = ctx.ReadBuffer();
- std::memcpy(&uuid, buffer.data() + 8, sizeof(u128));
-
- IPC::ResponseBuilder rb{ctx, 6, 0, 1};
-
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<INetworkProfile>(system);
- rb.PushRaw<u128>(uuid);
- }
- void GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
-
- struct IpConfigInfo {
- IpAddressSetting ip_address_setting{};
- DnsSetting dns_setting{};
- };
- static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting),
- "IpConfigInfo has incorrect size.");
-
- const auto net_iface = Network::GetSelectedNetworkInterface();
-
- const IpConfigInfo ip_config_info = [&net_iface] {
- if (!net_iface) {
- return IpConfigInfo{};
- }
-
- return IpConfigInfo{
+ return SfNetworkProfileData{
+ .ip_setting_data{
.ip_address_setting{
.is_automatic{true},
.current_address{Network::TranslateIPv4(net_iface->ip_address)},
@@ -400,46 +320,178 @@ private:
.primary_dns{1, 1, 1, 1},
.secondary_dns{1, 0, 0, 1},
},
- };
- }();
+ .proxy_setting{
+ .enabled{false},
+ .port{},
+ .proxy_server{},
+ .automatic_auth_enabled{},
+ .user{},
+ .password{},
+ },
+ .mtu{1500},
+ },
+ .uuid{0xdeadbeef, 0xdeadbeef},
+ .network_name{"yuzu Network"},
+ .wireless_setting_data{
+ .ssid_length{12},
+ .ssid{"yuzu Network"},
+ .passphrase{"yuzupassword"},
+ },
+ };
+ }();
- IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)};
- rb.Push(ResultSuccess);
- rb.PushRaw<IpConfigInfo>(ip_config_info);
+ // When we're connected to a room, spoof the hosts IP address
+ if (auto room_member = network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ network_profile_data.ip_setting_data.ip_address_setting.current_address =
+ room_member->GetFakeIpAddress();
+ }
}
- void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u8>(0);
+ ctx.WriteBuffer(network_profile_data);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IGeneralService::RemoveNetworkProfile(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IGeneralService::GetCurrentIpAddress(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+ auto ipv4 = Network::GetHostIPv4Address();
+ if (!ipv4) {
+ LOG_ERROR(Service_NIFM, "Couldn't get host IPv4 address, defaulting to 0.0.0.0");
+ ipv4.emplace(Network::IPv4Address{0, 0, 0, 0});
}
- void IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- if (Network::GetHostIPv4Address().has_value()) {
- rb.Push<u8>(1);
- } else {
- rb.Push<u8>(0);
+ // When we're connected to a room, spoof the hosts IP address
+ if (auto room_member = network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ ipv4 = room_member->GetFakeIpAddress();
}
}
- void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- if (Network::GetHostIPv4Address().has_value()) {
- rb.Push<u8>(1);
- } else {
- rb.Push<u8>(0);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(*ipv4);
+}
+
+void IGeneralService::CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NIFM, "called");
+
+ ASSERT_MSG(ctx.GetReadBufferSize() == 0x17c, "SfNetworkProfileData is not the correct size");
+ u128 uuid{};
+ auto buffer = ctx.ReadBuffer();
+ std::memcpy(&uuid, buffer.data() + 8, sizeof(u128));
+
+ IPC::ResponseBuilder rb{ctx, 6, 0, 1};
+
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<INetworkProfile>(system);
+ rb.PushRaw<u128>(uuid);
+}
+
+void IGeneralService::GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+ struct IpConfigInfo {
+ IpAddressSetting ip_address_setting{};
+ DnsSetting dns_setting{};
+ };
+ static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting),
+ "IpConfigInfo has incorrect size.");
+
+ const auto net_iface = Network::GetSelectedNetworkInterface();
+
+ IpConfigInfo ip_config_info = [&net_iface] {
+ if (!net_iface) {
+ return IpConfigInfo{};
+ }
+
+ return IpConfigInfo{
+ .ip_address_setting{
+ .is_automatic{true},
+ .current_address{Network::TranslateIPv4(net_iface->ip_address)},
+ .subnet_mask{Network::TranslateIPv4(net_iface->subnet_mask)},
+ .gateway{Network::TranslateIPv4(net_iface->gateway)},
+ },
+ .dns_setting{
+ .is_automatic{true},
+ .primary_dns{1, 1, 1, 1},
+ .secondary_dns{1, 0, 0, 1},
+ },
+ };
+ }();
+
+ // When we're connected to a room, spoof the hosts IP address
+ if (auto room_member = network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ ip_config_info.ip_address_setting.current_address = room_member->GetFakeIpAddress();
}
}
-};
+
+ IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)};
+ rb.Push(ResultSuccess);
+ rb.PushRaw<IpConfigInfo>(ip_config_info);
+}
+
+void IGeneralService::IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(1);
+}
+
+void IGeneralService::GetInternetConnectionStatus(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+ struct Output {
+ InternetConnectionType type{InternetConnectionType::WiFi};
+ u8 wifi_strength{3};
+ InternetConnectionStatus state{InternetConnectionStatus::Connected};
+ };
+ static_assert(sizeof(Output) == 0x3, "Output has incorrect size.");
+
+ constexpr Output out{};
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(out);
+}
+
+void IGeneralService::IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ if (Network::GetHostIPv4Address().has_value()) {
+ rb.Push<u8>(1);
+ } else {
+ rb.Push<u8>(0);
+ }
+}
+
+void IGeneralService::IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
+ LOG_ERROR(Service_NIFM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ if (Network::GetHostIPv4Address().has_value()) {
+ rb.Push<u8>(1);
+ } else {
+ rb.Push<u8>(0);
+ }
+}
IGeneralService::IGeneralService(Core::System& system_)
- : ServiceFramework{system_, "IGeneralService"} {
+ : ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} {
// clang-format off
static const FunctionInfo functions[] = {
{1, &IGeneralService::GetClientId, "GetClientId"},
@@ -458,7 +510,7 @@ IGeneralService::IGeneralService(Core::System& system_)
{15, &IGeneralService::GetCurrentIpConfigInfo, "GetCurrentIpConfigInfo"},
{16, nullptr, "SetWirelessCommunicationEnabled"},
{17, &IGeneralService::IsWirelessCommunicationEnabled, "IsWirelessCommunicationEnabled"},
- {18, nullptr, "GetInternetConnectionStatus"},
+ {18, &IGeneralService::GetInternetConnectionStatus, "GetInternetConnectionStatus"},
{19, nullptr, "SetEthernetCommunicationEnabled"},
{20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"},
{21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"},
@@ -490,6 +542,8 @@ IGeneralService::IGeneralService(Core::System& system_)
RegisterHandlers(functions);
}
+IGeneralService::~IGeneralService() = default;
+
class NetworkInterface final : public ServiceFramework<NetworkInterface> {
public:
explicit NetworkInterface(const char* name, Core::System& system_)
diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h
index c3dd4f386..48161be28 100644
--- a/src/core/hle/service/nifm/nifm.h
+++ b/src/core/hle/service/nifm/nifm.h
@@ -1,9 +1,13 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include "core/hle/service/service.h"
+#include "network/network.h"
+#include "network/room.h"
+#include "network/room_member.h"
+
namespace Core {
class System;
}
@@ -17,4 +21,26 @@ namespace Service::NIFM {
/// Registers all NIFM services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
+class IGeneralService final : public ServiceFramework<IGeneralService> {
+public:
+ explicit IGeneralService(Core::System& system_);
+ ~IGeneralService() override;
+
+private:
+ void GetClientId(Kernel::HLERequestContext& ctx);
+ void CreateScanRequest(Kernel::HLERequestContext& ctx);
+ void CreateRequest(Kernel::HLERequestContext& ctx);
+ void GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx);
+ void RemoveNetworkProfile(Kernel::HLERequestContext& ctx);
+ void GetCurrentIpAddress(Kernel::HLERequestContext& ctx);
+ void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx);
+ void GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx);
+ void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx);
+ void GetInternetConnectionStatus(Kernel::HLERequestContext& ctx);
+ void IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx);
+ void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx);
+
+ Network::RoomNetwork& network;
+};
+
} // namespace Service::NIFM
diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp
index 4fc23a958..b2bb7426d 100644
--- a/src/core/hle/service/nim/nim.cpp
+++ b/src/core/hle/service/nim/nim.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <ctime>
diff --git a/src/core/hle/service/nim/nim.h b/src/core/hle/service/nim/nim.h
index 571153fe6..8f6ff28e8 100644
--- a/src/core/hle/service/nim/nim.h
+++ b/src/core/hle/service/nim/nim.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/npns/npns.cpp b/src/core/hle/service/npns/npns.cpp
index 32533cd94..8133711c2 100644
--- a/src/core/hle/service/npns/npns.cpp
+++ b/src/core/hle/service/npns/npns.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
diff --git a/src/core/hle/service/npns/npns.h b/src/core/hle/service/npns/npns.h
index 3b7596b6b..84e6ec437 100644
--- a/src/core/hle/service/npns/npns.h
+++ b/src/core/hle/service/npns/npns.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/ns/errors.h b/src/core/hle/service/ns/errors.h
index f4aea8a65..8a7621798 100644
--- a/src/core/hle/service/ns/errors.h
+++ b/src/core/hle/service/ns/errors.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,5 +7,5 @@
namespace Service::NS {
-constexpr ResultCode ERR_APPLICATION_LANGUAGE_NOT_FOUND{ErrorModule::NS, 300};
+constexpr Result ERR_APPLICATION_LANGUAGE_NOT_FOUND{ErrorModule::NS, 300};
} \ No newline at end of file
diff --git a/src/core/hle/service/ns/iplatform_service_manager.cpp b/src/core/hle/service/ns/iplatform_service_manager.cpp
new file mode 100644
index 000000000..fd047ff26
--- /dev/null
+++ b/src/core/hle/service/ns/iplatform_service_manager.cpp
@@ -0,0 +1,299 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <cstring>
+#include <vector>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/swap.h"
+#include "core/core.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/romfs.h"
+#include "core/file_sys/system_archive/system_archive.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_shared_memory.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/physical_memory.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/ns/iplatform_service_manager.h"
+
+namespace Service::NS {
+
+struct FontRegion {
+ u32 offset;
+ u32 size;
+};
+
+// The below data is specific to shared font data dumped from Switch on f/w 2.2
+// Virtual address and offsets/sizes likely will vary by dump
+[[maybe_unused]] constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
+constexpr u32 EXPECTED_RESULT{0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be
+constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be
+constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000};
+constexpr FontRegion EMPTY_REGION{0, 0};
+
+enum class LoadState : u32 {
+ Loading = 0,
+ Done = 1,
+};
+
+static void DecryptSharedFont(const std::vector<u32>& input, Kernel::PhysicalMemory& output,
+ std::size_t& offset) {
+ ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,
+ "Shared fonts exceeds 17mb!");
+ ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number");
+
+ const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor
+ std::vector<u32> transformed_font(input.size());
+ // TODO(ogniK): Figure out a better way to do this
+ std::transform(input.begin(), input.end(), transformed_font.begin(),
+ [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); });
+ transformed_font[1] = Common::swap32(transformed_font[1]) ^ KEY; // "re-encrypt" the size
+ std::memcpy(output.data() + offset, transformed_font.data(),
+ transformed_font.size() * sizeof(u32));
+ offset += transformed_font.size() * sizeof(u32);
+}
+
+void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output) {
+ ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number");
+
+ if (input.size() < 2) {
+ LOG_ERROR(Service_NS, "Input font is empty");
+ return;
+ }
+
+ const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor
+ std::vector<u32> transformed_font(input.size());
+ // TODO(ogniK): Figure out a better way to do this
+ std::transform(input.begin(), input.end(), transformed_font.begin(),
+ [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); });
+ std::memcpy(output.data(), transformed_font.data() + 2,
+ (transformed_font.size() - 2) * sizeof(u32));
+}
+
+void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output,
+ std::size_t& offset) {
+ ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,
+ "Shared fonts exceeds 17mb!");
+
+ const auto key = Common::swap32(EXPECTED_RESULT ^ EXPECTED_MAGIC);
+ std::vector<u32> transformed_font(input.size() + 2);
+ transformed_font[0] = Common::swap32(EXPECTED_MAGIC);
+ transformed_font[1] = Common::swap32(static_cast<u32>(input.size() * sizeof(u32))) ^ key;
+ std::transform(input.begin(), input.end(), transformed_font.begin() + 2,
+ [key](u32 in) { return in ^ key; });
+ std::memcpy(output.data() + offset, transformed_font.data(),
+ transformed_font.size() * sizeof(u32));
+ offset += transformed_font.size() * sizeof(u32);
+}
+
+// Helper function to make BuildSharedFontsRawRegions a bit nicer
+static u32 GetU32Swapped(const u8* data) {
+ u32 value;
+ std::memcpy(&value, data, sizeof(value));
+ return Common::swap32(value);
+}
+
+struct IPlatformServiceManager::Impl {
+ const FontRegion& GetSharedFontRegion(std::size_t index) const {
+ if (index >= shared_font_regions.size() || shared_font_regions.empty()) {
+ // No font fallback
+ return EMPTY_REGION;
+ }
+ return shared_font_regions.at(index);
+ }
+
+ void BuildSharedFontsRawRegions(const Kernel::PhysicalMemory& input) {
+ // As we can derive the xor key we can just populate the offsets
+ // based on the shared memory dump
+ unsigned cur_offset = 0;
+
+ for (std::size_t i = 0; i < SHARED_FONTS.size(); i++) {
+ // Out of shared fonts/invalid font
+ if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT) {
+ break;
+ }
+
+ // Derive key withing inverse xor
+ const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^ EXPECTED_MAGIC;
+ const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY;
+ shared_font_regions.push_back(FontRegion{cur_offset + 8, SIZE});
+ cur_offset += SIZE + 8;
+ }
+ }
+
+ /// Backing memory for the shared font data
+ std::shared_ptr<Kernel::PhysicalMemory> shared_font;
+
+ // Automatically populated based on shared_fonts dump or system archives.
+ std::vector<FontRegion> shared_font_regions;
+};
+
+IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const char* service_name_)
+ : ServiceFramework{system_, service_name_}, impl{std::make_unique<Impl>()} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IPlatformServiceManager::RequestLoad, "RequestLoad"},
+ {1, &IPlatformServiceManager::GetLoadState, "GetLoadState"},
+ {2, &IPlatformServiceManager::GetSize, "GetSize"},
+ {3, &IPlatformServiceManager::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"},
+ {4, &IPlatformServiceManager::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"},
+ {5, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
+ {6, nullptr, "GetSharedFontInOrderOfPriorityForSystem"},
+ {100, nullptr, "RequestApplicationFunctionAuthorization"},
+ {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"},
+ {102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"},
+ {103, nullptr, "RefreshApplicationFunctionBlackListDebugRecord"},
+ {104, nullptr, "RequestApplicationFunctionAuthorizationByProgramId"},
+ {105, nullptr, "GetFunctionBlackListSystemVersionToAuthorize"},
+ {106, nullptr, "GetFunctionBlackListVersion"},
+ {1000, nullptr, "LoadNgWordDataForPlatformRegionChina"},
+ {1001, nullptr, "GetNgWordDataSizeForPlatformRegionChina"},
+ };
+ // clang-format on
+ RegisterHandlers(functions);
+
+ auto& fsc = system.GetFileSystemController();
+
+ // Attempt to load shared font data from disk
+ const auto* nand = fsc.GetSystemNANDContents();
+ std::size_t offset = 0;
+ // Rebuild shared fonts from data ncas or synthesize
+
+ impl->shared_font = std::make_shared<Kernel::PhysicalMemory>(SHARED_FONT_MEM_SIZE);
+ for (auto font : SHARED_FONTS) {
+ FileSys::VirtualFile romfs;
+ const auto nca =
+ nand->GetEntry(static_cast<u64>(font.first), FileSys::ContentRecordType::Data);
+ if (nca) {
+ romfs = nca->GetRomFS();
+ }
+
+ if (!romfs) {
+ romfs = FileSys::SystemArchive::SynthesizeSystemArchive(static_cast<u64>(font.first));
+ }
+
+ if (!romfs) {
+ LOG_ERROR(Service_NS, "Failed to find or synthesize {:016X}! Skipping", font.first);
+ continue;
+ }
+
+ const auto extracted_romfs = FileSys::ExtractRomFS(romfs);
+ if (!extracted_romfs) {
+ LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping", font.first);
+ continue;
+ }
+ const auto font_fp = extracted_romfs->GetFile(font.second);
+ if (!font_fp) {
+ LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping", font.first, font.second);
+ continue;
+ }
+ std::vector<u32> font_data_u32(font_fp->GetSize() / sizeof(u32));
+ font_fp->ReadBytes<u32>(font_data_u32.data(), font_fp->GetSize());
+ // We need to be BigEndian as u32s for the xor encryption
+ std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(),
+ Common::swap32);
+ // Font offset and size do not account for the header
+ const FontRegion region{static_cast<u32>(offset + 8),
+ static_cast<u32>((font_data_u32.size() * sizeof(u32)) - 8)};
+ DecryptSharedFont(font_data_u32, *impl->shared_font, offset);
+ impl->shared_font_regions.push_back(region);
+ }
+}
+
+IPlatformServiceManager::~IPlatformServiceManager() = default;
+
+void IPlatformServiceManager::RequestLoad(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u32 shared_font_type{rp.Pop<u32>()};
+ // Games don't call this so all fonts should be loaded
+ LOG_DEBUG(Service_NS, "called, shared_font_type={}", shared_font_type);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IPlatformServiceManager::GetLoadState(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u32 font_id{rp.Pop<u32>()};
+ LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u32>(static_cast<u32>(LoadState::Done));
+}
+
+void IPlatformServiceManager::GetSize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u32 font_id{rp.Pop<u32>()};
+ LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u32>(impl->GetSharedFontRegion(font_id).size);
+}
+
+void IPlatformServiceManager::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u32 font_id{rp.Pop<u32>()};
+ LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u32>(impl->GetSharedFontRegion(font_id).offset);
+}
+
+void IPlatformServiceManager::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
+ // Map backing memory for the font data
+ LOG_DEBUG(Service_NS, "called");
+
+ // Create shared font memory object
+ std::memcpy(kernel.GetFontSharedMem().GetPointer(), impl->shared_font->data(),
+ impl->shared_font->size());
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(&kernel.GetFontSharedMem());
+}
+
+void IPlatformServiceManager::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for
+ LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ std::vector<u32> font_codes;
+ std::vector<u32> font_offsets;
+ std::vector<u32> font_sizes;
+
+ // TODO(ogniK): Have actual priority order
+ for (std::size_t i = 0; i < impl->shared_font_regions.size(); i++) {
+ font_codes.push_back(static_cast<u32>(i));
+ auto region = impl->GetSharedFontRegion(i);
+ font_offsets.push_back(region.offset);
+ font_sizes.push_back(region.size);
+ }
+
+ // Resize buffers if game requests smaller size output.
+ font_codes.resize(
+ std::min<std::size_t>(font_codes.size(), ctx.GetWriteBufferSize(0) / sizeof(u32)));
+ font_offsets.resize(
+ std::min<std::size_t>(font_offsets.size(), ctx.GetWriteBufferSize(1) / sizeof(u32)));
+ font_sizes.resize(
+ std::min<std::size_t>(font_sizes.size(), ctx.GetWriteBufferSize(2) / sizeof(u32)));
+
+ ctx.WriteBuffer(font_codes, 0);
+ ctx.WriteBuffer(font_offsets, 1);
+ ctx.WriteBuffer(font_sizes, 2);
+
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(static_cast<u8>(LoadState::Done)); // Fonts Loaded
+ rb.Push<u32>(static_cast<u32>(font_codes.size()));
+}
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/iplatform_service_manager.h b/src/core/hle/service/ns/iplatform_service_manager.h
new file mode 100644
index 000000000..ed6eda89f
--- /dev/null
+++ b/src/core/hle/service/ns/iplatform_service_manager.h
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include "core/hle/service/service.h"
+
+namespace Service {
+
+namespace FileSystem {
+class FileSystemController;
+} // namespace FileSystem
+
+namespace NS {
+
+enum class FontArchives : u64 {
+ Extension = 0x0100000000000810,
+ Standard = 0x0100000000000811,
+ Korean = 0x0100000000000812,
+ ChineseTraditional = 0x0100000000000813,
+ ChineseSimple = 0x0100000000000814,
+};
+
+constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
+ std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"),
+ std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"),
+ std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"),
+ std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"),
+ std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"),
+ std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"),
+ std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"),
+};
+
+void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output);
+void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, std::size_t& offset);
+
+class IPlatformServiceManager final : public ServiceFramework<IPlatformServiceManager> {
+public:
+ explicit IPlatformServiceManager(Core::System& system_, const char* service_name_);
+ ~IPlatformServiceManager() override;
+
+private:
+ void RequestLoad(Kernel::HLERequestContext& ctx);
+ void GetLoadState(Kernel::HLERequestContext& ctx);
+ void GetSize(Kernel::HLERequestContext& ctx);
+ void GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx);
+ void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx);
+ void GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx);
+
+ struct Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+} // namespace NS
+
+} // namespace Service
diff --git a/src/core/hle/service/ns/language.cpp b/src/core/hle/service/ns/language.cpp
index e01c6be47..036a1e9b7 100644
--- a/src/core/hle/service/ns/language.cpp
+++ b/src/core/hle/service/ns/language.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/ns/language.h"
#include "core/hle/service/set/set.h"
diff --git a/src/core/hle/service/ns/language.h b/src/core/hle/service/ns/language.h
index 2cc8e4806..ab6b71029 100644
--- a/src/core/hle/service/ns/language.h
+++ b/src/core/hle/service/ns/language.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 5eaad0474..f7318c3cb 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "common/settings.h"
@@ -10,10 +9,10 @@
#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/ns/errors.h"
+#include "core/hle/service/ns/iplatform_service_manager.h"
#include "core/hle/service/ns/language.h"
#include "core/hle/service/ns/ns.h"
#include "core/hle/service/ns/pdm_qry.h"
-#include "core/hle/service/ns/pl_u.h"
#include "core/hle/service/set/set.h"
namespace Service::NS {
@@ -765,7 +764,8 @@ void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system
std::make_shared<PDM_QRY>(system)->InstallAsService(service_manager);
- std::make_shared<PL_U>(system)->InstallAsService(service_manager);
+ std::make_shared<IPlatformServiceManager>(system, "pl:s")->InstallAsService(service_manager);
+ std::make_shared<IPlatformServiceManager>(system, "pl:u")->InstallAsService(service_manager);
}
} // namespace Service::NS
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index 43540b0fb..4dc191518 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/ns/pdm_qry.cpp b/src/core/hle/service/ns/pdm_qry.cpp
index 36ce46353..aac8f573f 100644
--- a/src/core/hle/service/ns/pdm_qry.cpp
+++ b/src/core/hle/service/ns/pdm_qry.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
@@ -9,7 +8,6 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/ns/pdm_qry.h"
#include "core/hle/service/service.h"
-#include "core/hle/service/sm/sm.h"
namespace Service::NS {
diff --git a/src/core/hle/service/ns/pdm_qry.h b/src/core/hle/service/ns/pdm_qry.h
index 516136314..abcc3bef3 100644
--- a/src/core/hle/service/ns/pdm_qry.h
+++ b/src/core/hle/service/ns/pdm_qry.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
deleted file mode 100644
index 74cc45f1e..000000000
--- a/src/core/hle/service/ns/pl_u.cpp
+++ /dev/null
@@ -1,300 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <cstring>
-#include <vector>
-
-#include "common/assert.h"
-#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "common/swap.h"
-#include "core/core.h"
-#include "core/file_sys/content_archive.h"
-#include "core/file_sys/nca_metadata.h"
-#include "core/file_sys/registered_cache.h"
-#include "core/file_sys/romfs.h"
-#include "core/file_sys/system_archive/system_archive.h"
-#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/k_shared_memory.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/physical_memory.h"
-#include "core/hle/service/filesystem/filesystem.h"
-#include "core/hle/service/ns/pl_u.h"
-
-namespace Service::NS {
-
-struct FontRegion {
- u32 offset;
- u32 size;
-};
-
-// The below data is specific to shared font data dumped from Switch on f/w 2.2
-// Virtual address and offsets/sizes likely will vary by dump
-[[maybe_unused]] constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
-constexpr u32 EXPECTED_RESULT{0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be
-constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be
-constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000};
-constexpr FontRegion EMPTY_REGION{0, 0};
-
-enum class LoadState : u32 {
- Loading = 0,
- Done = 1,
-};
-
-static void DecryptSharedFont(const std::vector<u32>& input, Kernel::PhysicalMemory& output,
- std::size_t& offset) {
- ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,
- "Shared fonts exceeds 17mb!");
- ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number");
-
- const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor
- std::vector<u32> transformed_font(input.size());
- // TODO(ogniK): Figure out a better way to do this
- std::transform(input.begin(), input.end(), transformed_font.begin(),
- [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); });
- transformed_font[1] = Common::swap32(transformed_font[1]) ^ KEY; // "re-encrypt" the size
- std::memcpy(output.data() + offset, transformed_font.data(),
- transformed_font.size() * sizeof(u32));
- offset += transformed_font.size() * sizeof(u32);
-}
-
-void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output) {
- ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number");
-
- if (input.size() < 2) {
- LOG_ERROR(Service_NS, "Input font is empty");
- return;
- }
-
- const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor
- std::vector<u32> transformed_font(input.size());
- // TODO(ogniK): Figure out a better way to do this
- std::transform(input.begin(), input.end(), transformed_font.begin(),
- [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); });
- std::memcpy(output.data(), transformed_font.data() + 2,
- (transformed_font.size() - 2) * sizeof(u32));
-}
-
-void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output,
- std::size_t& offset) {
- ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,
- "Shared fonts exceeds 17mb!");
-
- const auto key = Common::swap32(EXPECTED_RESULT ^ EXPECTED_MAGIC);
- std::vector<u32> transformed_font(input.size() + 2);
- transformed_font[0] = Common::swap32(EXPECTED_MAGIC);
- transformed_font[1] = Common::swap32(static_cast<u32>(input.size() * sizeof(u32))) ^ key;
- std::transform(input.begin(), input.end(), transformed_font.begin() + 2,
- [key](u32 in) { return in ^ key; });
- std::memcpy(output.data() + offset, transformed_font.data(),
- transformed_font.size() * sizeof(u32));
- offset += transformed_font.size() * sizeof(u32);
-}
-
-// Helper function to make BuildSharedFontsRawRegions a bit nicer
-static u32 GetU32Swapped(const u8* data) {
- u32 value;
- std::memcpy(&value, data, sizeof(value));
- return Common::swap32(value);
-}
-
-struct PL_U::Impl {
- const FontRegion& GetSharedFontRegion(std::size_t index) const {
- if (index >= shared_font_regions.size() || shared_font_regions.empty()) {
- // No font fallback
- return EMPTY_REGION;
- }
- return shared_font_regions.at(index);
- }
-
- void BuildSharedFontsRawRegions(const Kernel::PhysicalMemory& input) {
- // As we can derive the xor key we can just populate the offsets
- // based on the shared memory dump
- unsigned cur_offset = 0;
-
- for (std::size_t i = 0; i < SHARED_FONTS.size(); i++) {
- // Out of shared fonts/invalid font
- if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT) {
- break;
- }
-
- // Derive key withing inverse xor
- const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^ EXPECTED_MAGIC;
- const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY;
- shared_font_regions.push_back(FontRegion{cur_offset + 8, SIZE});
- cur_offset += SIZE + 8;
- }
- }
-
- /// Backing memory for the shared font data
- std::shared_ptr<Kernel::PhysicalMemory> shared_font;
-
- // Automatically populated based on shared_fonts dump or system archives.
- std::vector<FontRegion> shared_font_regions;
-};
-
-PL_U::PL_U(Core::System& system_)
- : ServiceFramework{system_, "pl:u"}, impl{std::make_unique<Impl>()} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &PL_U::RequestLoad, "RequestLoad"},
- {1, &PL_U::GetLoadState, "GetLoadState"},
- {2, &PL_U::GetSize, "GetSize"},
- {3, &PL_U::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"},
- {4, &PL_U::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"},
- {5, &PL_U::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
- {6, nullptr, "GetSharedFontInOrderOfPriorityForSystem"},
- {100, nullptr, "RequestApplicationFunctionAuthorization"},
- {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"},
- {102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"},
- {103, nullptr, "RefreshApplicationFunctionBlackListDebugRecord"},
- {104, nullptr, "RequestApplicationFunctionAuthorizationByProgramId"},
- {105, nullptr, "GetFunctionBlackListSystemVersionToAuthorize"},
- {106, nullptr, "GetFunctionBlackListVersion"},
- {1000, nullptr, "LoadNgWordDataForPlatformRegionChina"},
- {1001, nullptr, "GetNgWordDataSizeForPlatformRegionChina"},
- };
- // clang-format on
- RegisterHandlers(functions);
-
- auto& fsc = system.GetFileSystemController();
-
- // Attempt to load shared font data from disk
- const auto* nand = fsc.GetSystemNANDContents();
- std::size_t offset = 0;
- // Rebuild shared fonts from data ncas or synthesize
-
- impl->shared_font = std::make_shared<Kernel::PhysicalMemory>(SHARED_FONT_MEM_SIZE);
- for (auto font : SHARED_FONTS) {
- FileSys::VirtualFile romfs;
- const auto nca =
- nand->GetEntry(static_cast<u64>(font.first), FileSys::ContentRecordType::Data);
- if (nca) {
- romfs = nca->GetRomFS();
- }
-
- if (!romfs) {
- romfs = FileSys::SystemArchive::SynthesizeSystemArchive(static_cast<u64>(font.first));
- }
-
- if (!romfs) {
- LOG_ERROR(Service_NS, "Failed to find or synthesize {:016X}! Skipping", font.first);
- continue;
- }
-
- const auto extracted_romfs = FileSys::ExtractRomFS(romfs);
- if (!extracted_romfs) {
- LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping", font.first);
- continue;
- }
- const auto font_fp = extracted_romfs->GetFile(font.second);
- if (!font_fp) {
- LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping", font.first, font.second);
- continue;
- }
- std::vector<u32> font_data_u32(font_fp->GetSize() / sizeof(u32));
- font_fp->ReadBytes<u32>(font_data_u32.data(), font_fp->GetSize());
- // We need to be BigEndian as u32s for the xor encryption
- std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(),
- Common::swap32);
- // Font offset and size do not account for the header
- const FontRegion region{static_cast<u32>(offset + 8),
- static_cast<u32>((font_data_u32.size() * sizeof(u32)) - 8)};
- DecryptSharedFont(font_data_u32, *impl->shared_font, offset);
- impl->shared_font_regions.push_back(region);
- }
-}
-
-PL_U::~PL_U() = default;
-
-void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u32 shared_font_type{rp.Pop<u32>()};
- // Games don't call this so all fonts should be loaded
- LOG_DEBUG(Service_NS, "called, shared_font_type={}", shared_font_type);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u32 font_id{rp.Pop<u32>()};
- LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(LoadState::Done));
-}
-
-void PL_U::GetSize(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u32 font_id{rp.Pop<u32>()};
- LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(impl->GetSharedFontRegion(font_id).size);
-}
-
-void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u32 font_id{rp.Pop<u32>()};
- LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(impl->GetSharedFontRegion(font_id).offset);
-}
-
-void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
- // Map backing memory for the font data
- LOG_DEBUG(Service_NS, "called");
-
- // Create shared font memory object
- std::memcpy(kernel.GetFontSharedMem().GetPointer(), impl->shared_font->data(),
- impl->shared_font->size());
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(&kernel.GetFontSharedMem());
-}
-
-void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for
- LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code);
-
- IPC::ResponseBuilder rb{ctx, 4};
- std::vector<u32> font_codes;
- std::vector<u32> font_offsets;
- std::vector<u32> font_sizes;
-
- // TODO(ogniK): Have actual priority order
- for (std::size_t i = 0; i < impl->shared_font_regions.size(); i++) {
- font_codes.push_back(static_cast<u32>(i));
- auto region = impl->GetSharedFontRegion(i);
- font_offsets.push_back(region.offset);
- font_sizes.push_back(region.size);
- }
-
- // Resize buffers if game requests smaller size output.
- font_codes.resize(
- std::min<std::size_t>(font_codes.size(), ctx.GetWriteBufferSize(0) / sizeof(u32)));
- font_offsets.resize(
- std::min<std::size_t>(font_offsets.size(), ctx.GetWriteBufferSize(1) / sizeof(u32)));
- font_sizes.resize(
- std::min<std::size_t>(font_sizes.size(), ctx.GetWriteBufferSize(2) / sizeof(u32)));
-
- ctx.WriteBuffer(font_codes, 0);
- ctx.WriteBuffer(font_offsets, 1);
- ctx.WriteBuffer(font_sizes, 2);
-
- rb.Push(ResultSuccess);
- rb.Push<u8>(static_cast<u8>(LoadState::Done)); // Fonts Loaded
- rb.Push<u32>(static_cast<u32>(font_codes.size()));
-}
-
-} // namespace Service::NS
diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h
deleted file mode 100644
index f920c7f69..000000000
--- a/src/core/hle/service/ns/pl_u.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <vector>
-#include "core/hle/service/service.h"
-
-namespace Service {
-
-namespace FileSystem {
-class FileSystemController;
-} // namespace FileSystem
-
-namespace NS {
-
-enum class FontArchives : u64 {
- Extension = 0x0100000000000810,
- Standard = 0x0100000000000811,
- Korean = 0x0100000000000812,
- ChineseTraditional = 0x0100000000000813,
- ChineseSimple = 0x0100000000000814,
-};
-
-constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
- std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"),
- std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"),
- std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"),
- std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"),
- std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"),
- std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"),
- std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"),
-};
-
-void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output);
-void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, std::size_t& offset);
-
-class PL_U final : public ServiceFramework<PL_U> {
-public:
- explicit PL_U(Core::System& system_);
- ~PL_U() override;
-
-private:
- void RequestLoad(Kernel::HLERequestContext& ctx);
- void GetLoadState(Kernel::HLERequestContext& ctx);
- void GetSize(Kernel::HLERequestContext& ctx);
- void GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx);
- void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx);
- void GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx);
-
- struct Impl;
- std::unique_ptr<Impl> impl;
-};
-
-} // namespace NS
-
-} // namespace Service
diff --git a/src/core/hle/service/nvdrv/core/container.cpp b/src/core/hle/service/nvdrv/core/container.cpp
new file mode 100644
index 000000000..37ca24f5d
--- /dev/null
+++ b/src/core/hle/service/nvdrv/core/container.cpp
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
+#include "video_core/host1x/host1x.h"
+
+namespace Service::Nvidia::NvCore {
+
+struct ContainerImpl {
+ explicit ContainerImpl(Tegra::Host1x::Host1x& host1x_)
+ : file{host1x_}, manager{host1x_}, device_file_data{} {}
+ NvMap file;
+ SyncpointManager manager;
+ Container::Host1xDeviceFileData device_file_data;
+};
+
+Container::Container(Tegra::Host1x::Host1x& host1x_) {
+ impl = std::make_unique<ContainerImpl>(host1x_);
+}
+
+Container::~Container() = default;
+
+NvMap& Container::GetNvMapFile() {
+ return impl->file;
+}
+
+const NvMap& Container::GetNvMapFile() const {
+ return impl->file;
+}
+
+Container::Host1xDeviceFileData& Container::Host1xDeviceFile() {
+ return impl->device_file_data;
+}
+
+const Container::Host1xDeviceFileData& Container::Host1xDeviceFile() const {
+ return impl->device_file_data;
+}
+
+SyncpointManager& Container::GetSyncpointManager() {
+ return impl->manager;
+}
+
+const SyncpointManager& Container::GetSyncpointManager() const {
+ return impl->manager;
+}
+
+} // namespace Service::Nvidia::NvCore
diff --git a/src/core/hle/service/nvdrv/core/container.h b/src/core/hle/service/nvdrv/core/container.h
new file mode 100644
index 000000000..b4b63ac90
--- /dev/null
+++ b/src/core/hle/service/nvdrv/core/container.h
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <deque>
+#include <memory>
+#include <unordered_map>
+
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Tegra::Host1x {
+class Host1x;
+} // namespace Tegra::Host1x
+
+namespace Service::Nvidia::NvCore {
+
+class NvMap;
+class SyncpointManager;
+
+struct ContainerImpl;
+
+class Container {
+public:
+ explicit Container(Tegra::Host1x::Host1x& host1x);
+ ~Container();
+
+ NvMap& GetNvMapFile();
+
+ const NvMap& GetNvMapFile() const;
+
+ SyncpointManager& GetSyncpointManager();
+
+ const SyncpointManager& GetSyncpointManager() const;
+
+ struct Host1xDeviceFileData {
+ std::unordered_map<DeviceFD, u32> fd_to_id{};
+ std::deque<u32> syncpts_accumulated{};
+ u32 nvdec_next_id{};
+ u32 vic_next_id{};
+ };
+
+ Host1xDeviceFileData& Host1xDeviceFile();
+
+ const Host1xDeviceFileData& Host1xDeviceFile() const;
+
+private:
+ std::unique_ptr<ContainerImpl> impl;
+};
+
+} // namespace Service::Nvidia::NvCore
diff --git a/src/core/hle/service/nvdrv/core/nvmap.cpp b/src/core/hle/service/nvdrv/core/nvmap.cpp
new file mode 100644
index 000000000..fbd8a74a5
--- /dev/null
+++ b/src/core/hle/service/nvdrv/core/nvmap.cpp
@@ -0,0 +1,272 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
+#include "core/memory.h"
+#include "video_core/host1x/host1x.h"
+
+using Core::Memory::YUZU_PAGESIZE;
+
+namespace Service::Nvidia::NvCore {
+NvMap::Handle::Handle(u64 size_, Id id_)
+ : size(size_), aligned_size(size), orig_size(size), id(id_) {
+ flags.raw = 0;
+}
+
+NvResult NvMap::Handle::Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress) {
+ std::scoped_lock lock(mutex);
+
+ // Handles cannot be allocated twice
+ if (allocated) {
+ return NvResult::AccessDenied;
+ }
+
+ flags = pFlags;
+ kind = pKind;
+ align = pAlign < YUZU_PAGESIZE ? YUZU_PAGESIZE : pAlign;
+
+ // This flag is only applicable for handles with an address passed
+ if (pAddress) {
+ flags.keep_uncached_after_free.Assign(0);
+ } else {
+ LOG_CRITICAL(Service_NVDRV,
+ "Mapping nvmap handles without a CPU side address is unimplemented!");
+ }
+
+ size = Common::AlignUp(size, YUZU_PAGESIZE);
+ aligned_size = Common::AlignUp(size, align);
+ address = pAddress;
+ allocated = true;
+
+ return NvResult::Success;
+}
+
+NvResult NvMap::Handle::Duplicate(bool internal_session) {
+ std::scoped_lock lock(mutex);
+ // Unallocated handles cannot be duplicated as duplication requires memory accounting (in HOS)
+ if (!allocated) [[unlikely]] {
+ return NvResult::BadValue;
+ }
+
+ // If we internally use FromId the duplication tracking of handles won't work accurately due to
+ // us not implementing per-process handle refs.
+ if (internal_session) {
+ internal_dupes++;
+ } else {
+ dupes++;
+ }
+
+ return NvResult::Success;
+}
+
+NvMap::NvMap(Tegra::Host1x::Host1x& host1x_) : host1x{host1x_} {}
+
+void NvMap::AddHandle(std::shared_ptr<Handle> handle_description) {
+ std::scoped_lock lock(handles_lock);
+
+ handles.emplace(handle_description->id, std::move(handle_description));
+}
+
+void NvMap::UnmapHandle(Handle& handle_description) {
+ // Remove pending unmap queue entry if needed
+ if (handle_description.unmap_queue_entry) {
+ unmap_queue.erase(*handle_description.unmap_queue_entry);
+ handle_description.unmap_queue_entry.reset();
+ }
+
+ // Free and unmap the handle from the SMMU
+ host1x.MemoryManager().Unmap(static_cast<GPUVAddr>(handle_description.pin_virt_address),
+ handle_description.aligned_size);
+ host1x.Allocator().Free(handle_description.pin_virt_address,
+ static_cast<u32>(handle_description.aligned_size));
+ handle_description.pin_virt_address = 0;
+}
+
+bool NvMap::TryRemoveHandle(const Handle& handle_description) {
+ // No dupes left, we can remove from handle map
+ if (handle_description.dupes == 0 && handle_description.internal_dupes == 0) {
+ std::scoped_lock lock(handles_lock);
+
+ auto it{handles.find(handle_description.id)};
+ if (it != handles.end()) {
+ handles.erase(it);
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+}
+
+NvResult NvMap::CreateHandle(u64 size, std::shared_ptr<NvMap::Handle>& result_out) {
+ if (!size) [[unlikely]] {
+ return NvResult::BadValue;
+ }
+
+ u32 id{next_handle_id.fetch_add(HandleIdIncrement, std::memory_order_relaxed)};
+ auto handle_description{std::make_shared<Handle>(size, id)};
+ AddHandle(handle_description);
+
+ result_out = handle_description;
+ return NvResult::Success;
+}
+
+std::shared_ptr<NvMap::Handle> NvMap::GetHandle(Handle::Id handle) {
+ std::scoped_lock lock(handles_lock);
+ try {
+ return handles.at(handle);
+ } catch (std::out_of_range&) {
+ return nullptr;
+ }
+}
+
+VAddr NvMap::GetHandleAddress(Handle::Id handle) {
+ std::scoped_lock lock(handles_lock);
+ try {
+ return handles.at(handle)->address;
+ } catch (std::out_of_range&) {
+ return 0;
+ }
+}
+
+u32 NvMap::PinHandle(NvMap::Handle::Id handle) {
+ auto handle_description{GetHandle(handle)};
+ if (!handle_description) [[unlikely]] {
+ return 0;
+ }
+
+ std::scoped_lock lock(handle_description->mutex);
+ if (!handle_description->pins) {
+ // If we're in the unmap queue we can just remove ourselves and return since we're already
+ // mapped
+ {
+ // Lock now to prevent our queue entry from being removed for allocation in-between the
+ // following check and erase
+ std::scoped_lock queueLock(unmap_queue_lock);
+ if (handle_description->unmap_queue_entry) {
+ unmap_queue.erase(*handle_description->unmap_queue_entry);
+ handle_description->unmap_queue_entry.reset();
+
+ handle_description->pins++;
+ return handle_description->pin_virt_address;
+ }
+ }
+
+ // If not then allocate some space and map it
+ u32 address{};
+ auto& smmu_allocator = host1x.Allocator();
+ auto& smmu_memory_manager = host1x.MemoryManager();
+ while (!(address =
+ smmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size)))) {
+ // Free handles until the allocation succeeds
+ std::scoped_lock queueLock(unmap_queue_lock);
+ if (auto freeHandleDesc{unmap_queue.front()}) {
+ // Handles in the unmap queue are guaranteed not to be pinned so don't bother
+ // checking if they are before unmapping
+ std::scoped_lock freeLock(freeHandleDesc->mutex);
+ if (handle_description->pin_virt_address)
+ UnmapHandle(*freeHandleDesc);
+ } else {
+ LOG_CRITICAL(Service_NVDRV, "Ran out of SMMU address space!");
+ }
+ }
+
+ smmu_memory_manager.Map(static_cast<GPUVAddr>(address), handle_description->address,
+ handle_description->aligned_size);
+ handle_description->pin_virt_address = address;
+ }
+
+ handle_description->pins++;
+ return handle_description->pin_virt_address;
+}
+
+void NvMap::UnpinHandle(Handle::Id handle) {
+ auto handle_description{GetHandle(handle)};
+ if (!handle_description) {
+ return;
+ }
+
+ std::scoped_lock lock(handle_description->mutex);
+ if (--handle_description->pins < 0) {
+ LOG_WARNING(Service_NVDRV, "Pin count imbalance detected!");
+ } else if (!handle_description->pins) {
+ std::scoped_lock queueLock(unmap_queue_lock);
+
+ // Add to the unmap queue allowing this handle's memory to be freed if needed
+ unmap_queue.push_back(handle_description);
+ handle_description->unmap_queue_entry = std::prev(unmap_queue.end());
+ }
+}
+
+void NvMap::DuplicateHandle(Handle::Id handle, bool internal_session) {
+ auto handle_description{GetHandle(handle)};
+ if (!handle_description) {
+ LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
+ return;
+ }
+
+ auto result = handle_description->Duplicate(internal_session);
+ if (result != NvResult::Success) {
+ LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
+ }
+}
+
+std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool internal_session) {
+ std::weak_ptr<Handle> hWeak{GetHandle(handle)};
+ FreeInfo freeInfo;
+
+ // We use a weak ptr here so we can tell when the handle has been freed and report that back to
+ // guest
+ if (auto handle_description = hWeak.lock()) {
+ std::scoped_lock lock(handle_description->mutex);
+
+ if (internal_session) {
+ if (--handle_description->internal_dupes < 0)
+ LOG_WARNING(Service_NVDRV, "Internal duplicate count imbalance detected!");
+ } else {
+ if (--handle_description->dupes < 0) {
+ LOG_WARNING(Service_NVDRV, "User duplicate count imbalance detected!");
+ } else if (handle_description->dupes == 0) {
+ // Force unmap the handle
+ if (handle_description->pin_virt_address) {
+ std::scoped_lock queueLock(unmap_queue_lock);
+ UnmapHandle(*handle_description);
+ }
+
+ handle_description->pins = 0;
+ }
+ }
+
+ // Try to remove the shared ptr to the handle from the map, if nothing else is using the
+ // handle then it will now be freed when `handle_description` goes out of scope
+ if (TryRemoveHandle(*handle_description)) {
+ LOG_DEBUG(Service_NVDRV, "Removed nvmap handle: {}", handle);
+ } else {
+ LOG_DEBUG(Service_NVDRV,
+ "Tried to free nvmap handle: {} but didn't as it still has duplicates",
+ handle);
+ }
+
+ freeInfo = {
+ .address = handle_description->address,
+ .size = handle_description->size,
+ .was_uncached = handle_description->flags.map_uncached.Value() != 0,
+ };
+ } else {
+ return std::nullopt;
+ }
+
+ // Handle hasn't been freed from memory, set address to 0 to mark that the handle wasn't freed
+ if (!hWeak.expired()) {
+ LOG_DEBUG(Service_NVDRV, "nvmap handle: {} wasn't freed as it is still in use", handle);
+ freeInfo.address = 0;
+ }
+
+ return freeInfo;
+}
+
+} // namespace Service::Nvidia::NvCore
diff --git a/src/core/hle/service/nvdrv/core/nvmap.h b/src/core/hle/service/nvdrv/core/nvmap.h
new file mode 100644
index 000000000..b9dd3801f
--- /dev/null
+++ b/src/core/hle/service/nvdrv/core/nvmap.h
@@ -0,0 +1,175 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <atomic>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <unordered_map>
+#include <assert.h>
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Tegra {
+
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
+
+} // namespace Tegra
+
+namespace Service::Nvidia::NvCore {
+/**
+ * @brief The nvmap core class holds the global state for nvmap and provides methods to manage
+ * handles
+ */
+class NvMap {
+public:
+ /**
+ * @brief A handle to a contiguous block of memory in an application's address space
+ */
+ struct Handle {
+ std::mutex mutex;
+
+ u64 align{}; //!< The alignment to use when pinning the handle onto the SMMU
+ u64 size; //!< Page-aligned size of the memory the handle refers to
+ u64 aligned_size; //!< `align`-aligned size of the memory the handle refers to
+ u64 orig_size; //!< Original unaligned size of the memory this handle refers to
+
+ s32 dupes{1}; //!< How many guest references there are to this handle
+ s32 internal_dupes{0}; //!< How many emulator-internal references there are to this handle
+
+ using Id = u32;
+ Id id; //!< A globally unique identifier for this handle
+
+ s32 pins{};
+ u32 pin_virt_address{};
+ std::optional<typename std::list<std::shared_ptr<Handle>>::iterator> unmap_queue_entry{};
+
+ union Flags {
+ u32 raw;
+ BitField<0, 1, u32> map_uncached; //!< If the handle should be mapped as uncached
+ BitField<2, 1, u32> keep_uncached_after_free; //!< Only applicable when the handle was
+ //!< allocated with a fixed address
+ BitField<4, 1, u32> _unk0_; //!< Passed to IOVMM for pins
+ } flags{};
+ static_assert(sizeof(Flags) == sizeof(u32));
+
+ u64 address{}; //!< The memory location in the guest's AS that this handle corresponds to,
+ //!< this can also be in the nvdrv tmem
+ bool is_shared_mem_mapped{}; //!< If this nvmap has been mapped with the MapSharedMem IPC
+ //!< call
+
+ u8 kind{}; //!< Used for memory compression
+ bool allocated{}; //!< If the handle has been allocated with `Alloc`
+
+ u64 dma_map_addr{}; //! remove me after implementing pinning.
+
+ Handle(u64 size, Id id);
+
+ /**
+ * @brief Sets up the handle with the given memory config, can allocate memory from the tmem
+ * if a 0 address is passed
+ */
+ [[nodiscard]] NvResult Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress);
+
+ /**
+ * @brief Increases the dupe counter of the handle for the given session
+ */
+ [[nodiscard]] NvResult Duplicate(bool internal_session);
+
+ /**
+ * @brief Obtains a pointer to the handle's memory and marks the handle it as having been
+ * mapped
+ */
+ u8* GetPointer() {
+ if (!address) {
+ return nullptr;
+ }
+
+ is_shared_mem_mapped = true;
+ return reinterpret_cast<u8*>(address);
+ }
+ };
+
+ /**
+ * @brief Encapsulates the result of a FreeHandle operation
+ */
+ struct FreeInfo {
+ u64 address; //!< Address the handle referred to before deletion
+ u64 size; //!< Page-aligned handle size
+ bool was_uncached; //!< If the handle was allocated as uncached
+ };
+
+ explicit NvMap(Tegra::Host1x::Host1x& host1x);
+
+ /**
+ * @brief Creates an unallocated handle of the given size
+ */
+ [[nodiscard]] NvResult CreateHandle(u64 size, std::shared_ptr<NvMap::Handle>& result_out);
+
+ std::shared_ptr<Handle> GetHandle(Handle::Id handle);
+
+ VAddr GetHandleAddress(Handle::Id handle);
+
+ /**
+ * @brief Maps a handle into the SMMU address space
+ * @note This operation is refcounted, the number of calls to this must eventually match the
+ * number of calls to `UnpinHandle`
+ * @return The SMMU virtual address that the handle has been mapped to
+ */
+ u32 PinHandle(Handle::Id handle);
+
+ /**
+ * @brief When this has been called an equal number of times to `PinHandle` for the supplied
+ * handle it will be added to a list of handles to be freed when necessary
+ */
+ void UnpinHandle(Handle::Id handle);
+
+ /**
+ * @brief Tries to duplicate a handle
+ */
+ void DuplicateHandle(Handle::Id handle, bool internal_session = false);
+
+ /**
+ * @brief Tries to free a handle and remove a single dupe
+ * @note If a handle has no dupes left and has no other users a FreeInfo struct will be returned
+ * describing the prior state of the handle
+ */
+ std::optional<FreeInfo> FreeHandle(Handle::Id handle, bool internal_session);
+
+private:
+ std::list<std::shared_ptr<Handle>> unmap_queue{};
+ std::mutex unmap_queue_lock{}; //!< Protects access to `unmap_queue`
+
+ std::unordered_map<Handle::Id, std::shared_ptr<Handle>>
+ handles{}; //!< Main owning map of handles
+ std::mutex handles_lock; //!< Protects access to `handles`
+
+ static constexpr u32 HandleIdIncrement{
+ 4}; //!< Each new handle ID is an increment of 4 from the previous
+ std::atomic<u32> next_handle_id{HandleIdIncrement};
+ Tegra::Host1x::Host1x& host1x;
+
+ void AddHandle(std::shared_ptr<Handle> handle);
+
+ /**
+ * @brief Unmaps and frees the SMMU memory region a handle is mapped to
+ * @note Both `unmap_queue_lock` and `handle_description.mutex` MUST be locked when calling this
+ */
+ void UnmapHandle(Handle& handle_description);
+
+ /**
+ * @brief Removes a handle from the map taking its dupes into account
+ * @note handle_description.mutex MUST be locked when calling this
+ * @return If the handle was removed from the map
+ */
+ bool TryRemoveHandle(const Handle& handle_description);
+};
+} // namespace Service::Nvidia::NvCore
diff --git a/src/core/hle/service/nvdrv/core/syncpoint_manager.cpp b/src/core/hle/service/nvdrv/core/syncpoint_manager.cpp
new file mode 100644
index 000000000..eda2041a0
--- /dev/null
+++ b/src/core/hle/service/nvdrv/core/syncpoint_manager.cpp
@@ -0,0 +1,121 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/assert.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
+#include "video_core/host1x/host1x.h"
+
+namespace Service::Nvidia::NvCore {
+
+SyncpointManager::SyncpointManager(Tegra::Host1x::Host1x& host1x_) : host1x{host1x_} {
+ constexpr u32 VBlank0SyncpointId{26};
+ constexpr u32 VBlank1SyncpointId{27};
+
+ // Reserve both vblank syncpoints as client managed as they use Continuous Mode
+ // Refer to section 14.3.5.3 of the TRM for more information on Continuous Mode
+ // https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/8f74a72394efb871cb3f886a3de2998cd7ff2990/drivers/gpu/host1x/drm/dc.c#L660
+ ReserveSyncpoint(VBlank0SyncpointId, true);
+ ReserveSyncpoint(VBlank1SyncpointId, true);
+
+ for (u32 syncpoint_id : channel_syncpoints) {
+ if (syncpoint_id) {
+ ReserveSyncpoint(syncpoint_id, false);
+ }
+ }
+}
+
+SyncpointManager::~SyncpointManager() = default;
+
+u32 SyncpointManager::ReserveSyncpoint(u32 id, bool client_managed) {
+ if (syncpoints.at(id).reserved) {
+ ASSERT_MSG(false, "Requested syncpoint is in use");
+ return 0;
+ }
+
+ syncpoints.at(id).reserved = true;
+ syncpoints.at(id).interface_managed = client_managed;
+
+ return id;
+}
+
+u32 SyncpointManager::FindFreeSyncpoint() {
+ for (u32 i{1}; i < syncpoints.size(); i++) {
+ if (!syncpoints[i].reserved) {
+ return i;
+ }
+ }
+ ASSERT_MSG(false, "Failed to find a free syncpoint!");
+ return 0;
+}
+
+u32 SyncpointManager::AllocateSyncpoint(bool client_managed) {
+ std::lock_guard lock(reservation_lock);
+ return ReserveSyncpoint(FindFreeSyncpoint(), client_managed);
+}
+
+void SyncpointManager::FreeSyncpoint(u32 id) {
+ std::lock_guard lock(reservation_lock);
+ ASSERT(syncpoints.at(id).reserved);
+ syncpoints.at(id).reserved = false;
+}
+
+bool SyncpointManager::IsSyncpointAllocated(u32 id) {
+ return (id <= SyncpointCount) && syncpoints[id].reserved;
+}
+
+bool SyncpointManager::HasSyncpointExpired(u32 id, u32 threshold) const {
+ const SyncpointInfo& syncpoint{syncpoints.at(id)};
+
+ if (!syncpoint.reserved) {
+ ASSERT(false);
+ return 0;
+ }
+
+ // If the interface manages counters then we don't keep track of the maximum value as it handles
+ // sanity checking the values then
+ if (syncpoint.interface_managed) {
+ return static_cast<s32>(syncpoint.counter_min - threshold) >= 0;
+ } else {
+ return (syncpoint.counter_max - threshold) >= (syncpoint.counter_min - threshold);
+ }
+}
+
+u32 SyncpointManager::IncrementSyncpointMaxExt(u32 id, u32 amount) {
+ if (!syncpoints.at(id).reserved) {
+ ASSERT(false);
+ return 0;
+ }
+
+ return syncpoints.at(id).counter_max += amount;
+}
+
+u32 SyncpointManager::ReadSyncpointMinValue(u32 id) {
+ if (!syncpoints.at(id).reserved) {
+ ASSERT(false);
+ return 0;
+ }
+
+ return syncpoints.at(id).counter_min;
+}
+
+u32 SyncpointManager::UpdateMin(u32 id) {
+ if (!syncpoints.at(id).reserved) {
+ ASSERT(false);
+ return 0;
+ }
+
+ syncpoints.at(id).counter_min = host1x.GetSyncpointManager().GetHostSyncpointValue(id);
+ return syncpoints.at(id).counter_min;
+}
+
+NvFence SyncpointManager::GetSyncpointFence(u32 id) {
+ if (!syncpoints.at(id).reserved) {
+ ASSERT(false);
+ return NvFence{};
+ }
+
+ return {.id = static_cast<s32>(id), .value = syncpoints.at(id).counter_max};
+}
+
+} // namespace Service::Nvidia::NvCore
diff --git a/src/core/hle/service/nvdrv/core/syncpoint_manager.h b/src/core/hle/service/nvdrv/core/syncpoint_manager.h
new file mode 100644
index 000000000..b76ef9032
--- /dev/null
+++ b/src/core/hle/service/nvdrv/core/syncpoint_manager.h
@@ -0,0 +1,134 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Tegra::Host1x {
+class Host1x;
+} // namespace Tegra::Host1x
+
+namespace Service::Nvidia::NvCore {
+
+enum class ChannelType : u32 {
+ MsEnc = 0,
+ VIC = 1,
+ GPU = 2,
+ NvDec = 3,
+ Display = 4,
+ NvJpg = 5,
+ TSec = 6,
+ Max = 7
+};
+
+/**
+ * @brief SyncpointManager handles allocating and accessing host1x syncpoints, these are cached
+ * versions of the HW syncpoints which are intermittently synced
+ * @note Refer to Chapter 14 of the Tegra X1 TRM for an exhaustive overview of them
+ * @url https://http.download.nvidia.com/tegra-public-appnotes/host1x.html
+ * @url
+ * https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/jetson-tx1/drivers/video/tegra/host/nvhost_syncpt.c
+ */
+class SyncpointManager final {
+public:
+ explicit SyncpointManager(Tegra::Host1x::Host1x& host1x);
+ ~SyncpointManager();
+
+ /**
+ * @brief Checks if the given syncpoint is both allocated and below the number of HW syncpoints
+ */
+ bool IsSyncpointAllocated(u32 id);
+
+ /**
+ * @brief Finds a free syncpoint and reserves it
+ * @return The ID of the reserved syncpoint
+ */
+ u32 AllocateSyncpoint(bool client_managed);
+
+ /**
+ * @url
+ * https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/8f74a72394efb871cb3f886a3de2998cd7ff2990/drivers/gpu/host1x/syncpt.c#L259
+ */
+ bool HasSyncpointExpired(u32 id, u32 threshold) const;
+
+ bool IsFenceSignalled(NvFence fence) const {
+ return HasSyncpointExpired(fence.id, fence.value);
+ }
+
+ /**
+ * @brief Atomically increments the maximum value of a syncpoint by the given amount
+ * @return The new max value of the syncpoint
+ */
+ u32 IncrementSyncpointMaxExt(u32 id, u32 amount);
+
+ /**
+ * @return The minimum value of the syncpoint
+ */
+ u32 ReadSyncpointMinValue(u32 id);
+
+ /**
+ * @brief Synchronises the minimum value of the syncpoint to with the GPU
+ * @return The new minimum value of the syncpoint
+ */
+ u32 UpdateMin(u32 id);
+
+ /**
+ * @brief Frees the usage of a syncpoint.
+ */
+ void FreeSyncpoint(u32 id);
+
+ /**
+ * @return A fence that will be signalled once this syncpoint hits its maximum value
+ */
+ NvFence GetSyncpointFence(u32 id);
+
+ static constexpr std::array<u32, static_cast<u32>(ChannelType::Max)> channel_syncpoints{
+ 0x0, // `MsEnc` is unimplemented
+ 0xC, // `VIC`
+ 0x0, // `GPU` syncpoints are allocated per-channel instead
+ 0x36, // `NvDec`
+ 0x0, // `Display` is unimplemented
+ 0x37, // `NvJpg`
+ 0x0, // `TSec` is unimplemented
+ }; //!< Maps each channel ID to a constant syncpoint
+
+private:
+ /**
+ * @note reservation_lock should be locked when calling this
+ */
+ u32 ReserveSyncpoint(u32 id, bool client_managed);
+
+ /**
+ * @return The ID of the first free syncpoint
+ */
+ u32 FindFreeSyncpoint();
+
+ struct SyncpointInfo {
+ std::atomic<u32> counter_min; //!< The least value the syncpoint can be (The value it was
+ //!< when it was last synchronized with host1x)
+ std::atomic<u32> counter_max; //!< The maximum value the syncpoint can reach according to
+ //!< the current usage
+ bool interface_managed; //!< If the syncpoint is managed by a host1x client interface, a
+ //!< client interface is a HW block that can handle host1x
+ //!< transactions on behalf of a host1x client (Which would
+ //!< otherwise need to be manually synced using PIO which is
+ //!< synchronous and requires direct cooperation of the CPU)
+ bool reserved; //!< If the syncpoint is reserved or not, not to be confused with a reserved
+ //!< value
+ };
+
+ constexpr static std::size_t SyncpointCount{192};
+ std::array<SyncpointInfo, SyncpointCount> syncpoints{};
+ std::mutex reservation_lock;
+
+ Tegra::Host1x::Host1x& host1x;
+};
+
+} // namespace Service::Nvidia::NvCore
diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h
index 3d874243a..204b0e757 100644
--- a/src/core/hle/service/nvdrv/devices/nvdevice.h
+++ b/src/core/hle/service/nvdrv/devices/nvdevice.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -12,6 +11,10 @@ namespace Core {
class System;
}
+namespace Kernel {
+class KEvent;
+}
+
namespace Service::Nvidia::Devices {
/// Represents an abstract nvidia device node. It is to be subclassed by concrete device nodes to
@@ -65,6 +68,10 @@ public:
*/
virtual void OnClose(DeviceFD fd) = 0;
+ virtual Kernel::KEvent* QueryEvent(u32 event_id) {
+ return nullptr;
+ }
+
protected:
Core::System& system;
};
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index 68f1e9060..4122fc98d 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -1,20 +1,20 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
-#include "core/hle/service/nvdrv/devices/nvmap.h"
#include "core/perf_stats.h"
#include "video_core/gpu.h"
namespace Service::Nvidia::Devices {
-nvdisp_disp0::nvdisp_disp0(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_)
- : nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)} {}
+nvdisp_disp0::nvdisp_disp0(Core::System& system_, NvCore::Container& core)
+ : nvdevice{system_}, container{core}, nvmap{core.GetNvMapFile()} {}
nvdisp_disp0::~nvdisp_disp0() = default;
NvResult nvdisp_disp0::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -38,23 +38,27 @@ 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,
- const Common::Rectangle<int>& crop_rect) {
- const VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle);
+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,
+ std::array<Service::Nvidia::NvFence, 4>& fences, u32 num_fences) {
+ const VAddr addr = nvmap.GetHandleAddress(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.GPU().RequestSwapBuffers(&framebuffer, fences, num_fences);
system.GetPerfStats().EndSystemFrame();
- system.GPU().SwapBuffers(&framebuffer);
system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs());
system.GetPerfStats().BeginSystemFrame();
}
+Kernel::KEvent* nvdisp_disp0::QueryEvent(u32 event_id) {
+ LOG_CRITICAL(Service_NVDRV, "Unknown DISP Event {}", event_id);
+ return nullptr;
+}
+
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
index de01e1d5f..04217ab12 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -9,7 +8,13 @@
#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::NvCore {
+class Container;
+class NvMap;
+} // namespace Service::Nvidia::NvCore
namespace Service::Nvidia::Devices {
@@ -17,7 +22,7 @@ class nvmap;
class nvdisp_disp0 final : public nvdevice {
public:
- explicit nvdisp_disp0(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_);
+ explicit nvdisp_disp0(Core::System& system_, NvCore::Container& core);
~nvdisp_disp0() override;
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -31,12 +36,16 @@ 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,
- const Common::Rectangle<int>& crop_rect);
+ 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,
+ std::array<Service::Nvidia::NvFence, 4>& fences, u32 num_fences);
+
+ Kernel::KEvent* QueryEvent(u32 event_id) override;
private:
- std::shared_ptr<nvmap> nvmap_dev;
+ NvCore::Container& container;
+ NvCore::NvMap& nvmap;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
index 85170cdb3..6411dbf43 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -1,22 +1,30 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
#include <cstring>
#include <utility>
+#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
-#include "core/hle/service/nvdrv/devices/nvmap.h"
+#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
+#include "video_core/control/channel_state.h"
+#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
namespace Service::Nvidia::Devices {
-nvhost_as_gpu::nvhost_as_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_)
- : nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)} {}
+nvhost_as_gpu::nvhost_as_gpu(Core::System& system_, Module& module_, NvCore::Container& core)
+ : nvdevice{system_}, module{module_}, container{core}, nvmap{core.GetNvMapFile()}, vm{},
+ gmmu{} {}
+
nvhost_as_gpu::~nvhost_as_gpu() = default;
NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -83,12 +91,52 @@ NvResult nvhost_as_gpu::AllocAsEx(const std::vector<u8>& input, std::vector<u8>&
IoctlAllocAsEx params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x{:X}", params.big_page_size);
- if (params.big_page_size == 0) {
- params.big_page_size = DEFAULT_BIG_PAGE_SIZE;
+ LOG_DEBUG(Service_NVDRV, "called, big_page_size=0x{:X}", params.big_page_size);
+
+ std::scoped_lock lock(mutex);
+
+ if (vm.initialised) {
+ ASSERT_MSG(false, "Cannot initialise an address space twice!");
+ return NvResult::InvalidState;
+ }
+
+ if (params.big_page_size) {
+ if (!std::has_single_bit(params.big_page_size)) {
+ LOG_ERROR(Service_NVDRV, "Non power-of-2 big page size: 0x{:X}!", params.big_page_size);
+ return NvResult::BadValue;
+ }
+
+ if ((params.big_page_size & VM::SUPPORTED_BIG_PAGE_SIZES) == 0) {
+ LOG_ERROR(Service_NVDRV, "Unsupported big page size: 0x{:X}!", params.big_page_size);
+ return NvResult::BadValue;
+ }
+
+ vm.big_page_size = params.big_page_size;
+ vm.big_page_size_bits = static_cast<u32>(std::countr_zero(params.big_page_size));
+
+ vm.va_range_start = params.big_page_size << VM::VA_START_SHIFT;
+ }
+
+ // If this is unspecified then default values should be used
+ if (params.va_range_start) {
+ vm.va_range_start = params.va_range_start;
+ vm.va_range_split = params.va_range_split;
+ vm.va_range_end = params.va_range_end;
}
- big_page_size = params.big_page_size;
+ const auto start_pages{static_cast<u32>(vm.va_range_start >> VM::PAGE_SIZE_BITS)};
+ const auto end_pages{static_cast<u32>(vm.va_range_split >> VM::PAGE_SIZE_BITS)};
+ vm.small_page_allocator = std::make_shared<VM::Allocator>(start_pages, end_pages);
+
+ const auto start_big_pages{static_cast<u32>(vm.va_range_split >> vm.big_page_size_bits)};
+ const auto end_big_pages{
+ static_cast<u32>((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)};
+ vm.big_page_allocator = std::make_unique<VM::Allocator>(start_big_pages, end_big_pages);
+
+ gmmu = std::make_shared<Tegra::MemoryManager>(system, 40, vm.big_page_size_bits,
+ VM::PAGE_SIZE_BITS);
+ system.GPU().InitAddressSpace(*gmmu);
+ vm.initialised = true;
return NvResult::Success;
}
@@ -100,21 +148,76 @@ NvResult nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<
LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages,
params.page_size, params.flags);
- const auto size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
- if ((params.flags & AddressSpaceFlags::FixedOffset) != AddressSpaceFlags::None) {
- params.offset = *system.GPU().MemoryManager().AllocateFixed(params.offset, size);
+ std::scoped_lock lock(mutex);
+
+ if (!vm.initialised) {
+ return NvResult::BadValue;
+ }
+
+ if (params.page_size != VM::YUZU_PAGESIZE && params.page_size != vm.big_page_size) {
+ return NvResult::BadValue;
+ }
+
+ if (params.page_size != vm.big_page_size &&
+ ((params.flags & MappingFlags::Sparse) != MappingFlags::None)) {
+ UNIMPLEMENTED_MSG("Sparse small pages are not implemented!");
+ return NvResult::NotImplemented;
+ }
+
+ const u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
+ : vm.big_page_size_bits};
+
+ auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
+ : *vm.big_page_allocator};
+
+ if ((params.flags & MappingFlags::Fixed) != MappingFlags::None) {
+ allocator.AllocateFixed(static_cast<u32>(params.offset >> page_size_bits), params.pages);
} else {
- params.offset = system.GPU().MemoryManager().Allocate(size, params.align);
+ params.offset = static_cast<u64>(allocator.Allocate(params.pages)) << page_size_bits;
+ if (!params.offset) {
+ ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!");
+ return NvResult::InsufficientMemory;
+ }
}
- auto result = NvResult::Success;
- if (!params.offset) {
- LOG_CRITICAL(Service_NVDRV, "allocation failed for size {}", size);
- result = NvResult::InsufficientMemory;
+ u64 size{static_cast<u64>(params.pages) * params.page_size};
+
+ if ((params.flags & MappingFlags::Sparse) != MappingFlags::None) {
+ gmmu->MapSparse(params.offset, size);
}
+ allocation_map[params.offset] = {
+ .size = size,
+ .mappings{},
+ .page_size = params.page_size,
+ .sparse = (params.flags & MappingFlags::Sparse) != MappingFlags::None,
+ .big_pages = params.page_size != VM::YUZU_PAGESIZE,
+ };
+
std::memcpy(output.data(), &params, output.size());
- return result;
+ return NvResult::Success;
+}
+
+void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
+ auto mapping{mapping_map.at(offset)};
+
+ if (!mapping->fixed) {
+ auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
+ u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
+
+ allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
+ static_cast<u32>(mapping->size >> page_size_bits));
+ }
+
+ // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
+ // Only FreeSpace can unmap them fully
+ if (mapping->sparse_alloc) {
+ gmmu->MapSparse(offset, mapping->size, mapping->big_page);
+ } else {
+ gmmu->Unmap(offset, mapping->size);
+ }
+
+ mapping_map.erase(offset);
}
NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -124,8 +227,40 @@ NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>&
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
params.pages, params.page_size);
- system.GPU().MemoryManager().Unmap(params.offset,
- static_cast<std::size_t>(params.pages) * params.page_size);
+ std::scoped_lock lock(mutex);
+
+ if (!vm.initialised) {
+ return NvResult::BadValue;
+ }
+
+ try {
+ auto allocation{allocation_map[params.offset]};
+
+ if (allocation.page_size != params.page_size ||
+ allocation.size != (static_cast<u64>(params.pages) * params.page_size)) {
+ return NvResult::BadValue;
+ }
+
+ for (const auto& mapping : allocation.mappings) {
+ FreeMappingLocked(mapping->offset);
+ }
+
+ // Unset sparse flag if required
+ if (allocation.sparse) {
+ gmmu->Unmap(params.offset, allocation.size);
+ }
+
+ auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
+ : *vm.big_page_allocator};
+ u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
+ : vm.big_page_size_bits};
+
+ allocator.Free(static_cast<u32>(params.offset >> page_size_bits),
+ static_cast<u32>(allocation.size >> page_size_bits));
+ allocation_map.erase(params.offset);
+ } catch (const std::out_of_range&) {
+ return NvResult::BadValue;
+ }
std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
@@ -136,35 +271,52 @@ NvResult nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& out
LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries);
- auto result = NvResult::Success;
std::vector<IoctlRemapEntry> entries(num_entries);
std::memcpy(entries.data(), input.data(), input.size());
+ std::scoped_lock lock(mutex);
+
+ if (!vm.initialised) {
+ return NvResult::BadValue;
+ }
+
for (const auto& entry : entries) {
- LOG_DEBUG(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
- entry.offset, entry.nvmap_handle, entry.pages);
+ GPUVAddr virtual_address{static_cast<u64>(entry.as_offset_big_pages)
+ << vm.big_page_size_bits};
+ u64 size{static_cast<u64>(entry.big_pages) << vm.big_page_size_bits};
- const auto object{nvmap_dev->GetObject(entry.nvmap_handle)};
- if (!object) {
- LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", entry.nvmap_handle);
- result = NvResult::InvalidState;
- break;
+ auto alloc{allocation_map.upper_bound(virtual_address)};
+
+ if (alloc-- == allocation_map.begin() ||
+ (virtual_address - alloc->first) + size > alloc->second.size) {
+ LOG_WARNING(Service_NVDRV, "Cannot remap into an unallocated region!");
+ return NvResult::BadValue;
}
- const auto offset{static_cast<GPUVAddr>(entry.offset) << 0x10};
- const auto size{static_cast<u64>(entry.pages) << 0x10};
- const auto map_offset{static_cast<u64>(entry.map_offset) << 0x10};
- const auto addr{system.GPU().MemoryManager().Map(object->addr + map_offset, offset, size)};
+ if (!alloc->second.sparse) {
+ LOG_WARNING(Service_NVDRV, "Cannot remap a non-sparse mapping!");
+ return NvResult::BadValue;
+ }
- if (!addr) {
- LOG_CRITICAL(Service_NVDRV, "map returned an invalid address!");
- result = NvResult::InvalidState;
- break;
+ const bool use_big_pages = alloc->second.big_pages;
+ if (!entry.handle) {
+ gmmu->MapSparse(virtual_address, size, use_big_pages);
+ } else {
+ auto handle{nvmap.GetHandle(entry.handle)};
+ if (!handle) {
+ return NvResult::BadValue;
+ }
+
+ VAddr cpu_address{static_cast<VAddr>(
+ handle->address +
+ (static_cast<u64>(entry.handle_offset_big_pages) << vm.big_page_size_bits))};
+
+ gmmu->Map(virtual_address, cpu_address, size, use_big_pages);
}
}
std::memcpy(output.data(), entries.data(), output.size());
- return result;
+ return NvResult::Success;
}
NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -174,79 +326,98 @@ NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8
LOG_DEBUG(Service_NVDRV,
"called, flags={:X}, nvmap_handle={:X}, buffer_offset={}, mapping_size={}"
", offset={}",
- params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size,
+ params.flags, params.handle, params.buffer_offset, params.mapping_size,
params.offset);
- const auto object{nvmap_dev->GetObject(params.nvmap_handle)};
- if (!object) {
- LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", params.nvmap_handle);
- std::memcpy(output.data(), &params, output.size());
- return NvResult::InvalidState;
- }
-
- // The real nvservices doesn't make a distinction between handles and ids, and
- // object can only have one handle and it will be the same as its id. Assert that this is the
- // case to prevent unexpected behavior.
- ASSERT(object->id == params.nvmap_handle);
- auto& gpu = system.GPU();
+ std::scoped_lock lock(mutex);
- u64 page_size{params.page_size};
- if (!page_size) {
- page_size = object->align;
+ if (!vm.initialised) {
+ return NvResult::BadValue;
}
- if ((params.flags & AddressSpaceFlags::Remap) != AddressSpaceFlags::None) {
- if (const auto buffer_map{FindBufferMap(params.offset)}; buffer_map) {
- const auto cpu_addr{static_cast<VAddr>(buffer_map->CpuAddr() + params.buffer_offset)};
- const auto gpu_addr{static_cast<GPUVAddr>(params.offset + params.buffer_offset)};
+ // Remaps a subregion of an existing mapping to a different PA
+ if ((params.flags & MappingFlags::Remap) != MappingFlags::None) {
+ try {
+ auto mapping{mapping_map.at(params.offset)};
- if (!gpu.MemoryManager().Map(cpu_addr, gpu_addr, params.mapping_size)) {
- LOG_CRITICAL(Service_NVDRV,
- "remap failed, flags={:X}, nvmap_handle={:X}, buffer_offset={}, "
- "mapping_size = {}, offset={}",
- params.flags, params.nvmap_handle, params.buffer_offset,
- params.mapping_size, params.offset);
-
- std::memcpy(output.data(), &params, output.size());
- return NvResult::InvalidState;
+ if (mapping->size < params.mapping_size) {
+ LOG_WARNING(Service_NVDRV,
+ "Cannot remap a partially mapped GPU address space region: 0x{:X}",
+ params.offset);
+ return NvResult::BadValue;
}
- std::memcpy(output.data(), &params, output.size());
- return NvResult::Success;
- } else {
- LOG_CRITICAL(Service_NVDRV, "address not mapped offset={}", params.offset);
+ u64 gpu_address{static_cast<u64>(params.offset + params.buffer_offset)};
+ VAddr cpu_address{mapping->ptr + params.buffer_offset};
+
+ gmmu->Map(gpu_address, cpu_address, params.mapping_size, mapping->big_page);
- std::memcpy(output.data(), &params, output.size());
- return NvResult::InvalidState;
+ return NvResult::Success;
+ } catch (const std::out_of_range&) {
+ LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: 0x{:X}",
+ params.offset);
+ return NvResult::BadValue;
}
}
- // We can only map objects that have already been assigned a CPU address.
- ASSERT(object->status == nvmap::Object::Status::Allocated);
-
- const auto physical_address{object->addr + params.buffer_offset};
- u64 size{params.mapping_size};
- if (!size) {
- size = object->size;
+ auto handle{nvmap.GetHandle(params.handle)};
+ if (!handle) {
+ return NvResult::BadValue;
}
- const bool is_alloc{(params.flags & AddressSpaceFlags::FixedOffset) == AddressSpaceFlags::None};
- if (is_alloc) {
- params.offset = gpu.MemoryManager().MapAllocate(physical_address, size, page_size);
- } else {
- params.offset = gpu.MemoryManager().Map(physical_address, params.offset, size);
- }
+ VAddr cpu_address{static_cast<VAddr>(handle->address + params.buffer_offset)};
+ u64 size{params.mapping_size ? params.mapping_size : handle->orig_size};
+
+ bool big_page{[&]() {
+ if (Common::IsAligned(handle->align, vm.big_page_size)) {
+ return true;
+ } else if (Common::IsAligned(handle->align, VM::YUZU_PAGESIZE)) {
+ return false;
+ } else {
+ ASSERT(false);
+ return false;
+ }
+ }()};
+
+ if ((params.flags & MappingFlags::Fixed) != MappingFlags::None) {
+ auto alloc{allocation_map.upper_bound(params.offset)};
- auto result = NvResult::Success;
- if (!params.offset) {
- LOG_CRITICAL(Service_NVDRV, "failed to map size={}", size);
- result = NvResult::InvalidState;
+ if (alloc-- == allocation_map.begin() ||
+ (params.offset - alloc->first) + size > alloc->second.size) {
+ ASSERT_MSG(false, "Cannot perform a fixed mapping into an unallocated region!");
+ return NvResult::BadValue;
+ }
+
+ const bool use_big_pages = alloc->second.big_pages && big_page;
+ gmmu->Map(params.offset, cpu_address, size, use_big_pages);
+
+ auto mapping{std::make_shared<Mapping>(cpu_address, params.offset, size, true,
+ use_big_pages, alloc->second.sparse)};
+ alloc->second.mappings.push_back(mapping);
+ mapping_map[params.offset] = mapping;
} else {
- AddBufferMap(params.offset, size, physical_address, is_alloc);
+
+ auto& allocator{big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
+ u32 page_size{big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
+ u32 page_size_bits{big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
+
+ params.offset = static_cast<u64>(allocator.Allocate(
+ static_cast<u32>(Common::AlignUp(size, page_size) >> page_size_bits)))
+ << page_size_bits;
+ if (!params.offset) {
+ ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!");
+ return NvResult::InsufficientMemory;
+ }
+
+ gmmu->Map(params.offset, cpu_address, Common::AlignUp(size, page_size), big_page);
+
+ auto mapping{
+ std::make_shared<Mapping>(cpu_address, params.offset, size, false, big_page, false)};
+ mapping_map[params.offset] = mapping;
}
std::memcpy(output.data(), &params, output.size());
- return result;
+ return NvResult::Success;
}
NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -255,47 +426,82 @@ NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8
LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
- if (const auto size{RemoveBufferMap(params.offset)}; size) {
- system.GPU().MemoryManager().Unmap(params.offset, *size);
- } else {
- LOG_ERROR(Service_NVDRV, "invalid offset=0x{:X}", params.offset);
+ std::scoped_lock lock(mutex);
+
+ if (!vm.initialised) {
+ return NvResult::BadValue;
+ }
+
+ try {
+ auto mapping{mapping_map.at(params.offset)};
+
+ if (!mapping->fixed) {
+ auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
+ u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
+
+ allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
+ static_cast<u32>(mapping->size >> page_size_bits));
+ }
+
+ // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
+ // Only FreeSpace can unmap them fully
+ if (mapping->sparse_alloc) {
+ gmmu->MapSparse(params.offset, mapping->size, mapping->big_page);
+ } else {
+ gmmu->Unmap(params.offset, mapping->size);
+ }
+
+ mapping_map.erase(params.offset);
+ } catch (const std::out_of_range&) {
+ LOG_WARNING(Service_NVDRV, "Couldn't find region to unmap at 0x{:X}", params.offset);
}
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
NvResult nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlBindChannel params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}", params.fd);
+ LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd);
- channel = params.fd;
+ auto gpu_channel_device = module.GetDevice<nvhost_gpu>(params.fd);
+ gpu_channel_device->channel_state->memory_manager = gmmu;
return NvResult::Success;
}
+void nvhost_as_gpu::GetVARegionsImpl(IoctlGetVaRegions& params) {
+ params.buf_size = 2 * sizeof(VaRegion);
+
+ params.regions = std::array<VaRegion, 2>{
+ VaRegion{
+ .offset = vm.small_page_allocator->GetVAStart() << VM::PAGE_SIZE_BITS,
+ .page_size = VM::YUZU_PAGESIZE,
+ ._pad0_{},
+ .pages = vm.small_page_allocator->GetVALimit() - vm.small_page_allocator->GetVAStart(),
+ },
+ VaRegion{
+ .offset = vm.big_page_allocator->GetVAStart() << vm.big_page_size_bits,
+ .page_size = vm.big_page_size,
+ ._pad0_{},
+ .pages = vm.big_page_allocator->GetVALimit() - vm.big_page_allocator->GetVAStart(),
+ },
+ };
+}
+
NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlGetVaRegions params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
- params.buf_size);
-
- params.buf_size = 0x30;
+ LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
+ params.buf_size);
- params.small = IoctlVaRegion{
- .offset = 0x04000000,
- .page_size = DEFAULT_SMALL_PAGE_SIZE,
- .pages = 0x3fbfff,
- };
+ std::scoped_lock lock(mutex);
- params.big = IoctlVaRegion{
- .offset = 0x04000000,
- .page_size = big_page_size,
- .pages = 0x1bffff,
- };
+ if (!vm.initialised) {
+ return NvResult::BadValue;
+ }
- // TODO(ogniK): This probably can stay stubbed but should add support way way later
+ GetVARegionsImpl(params);
std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
@@ -306,62 +512,27 @@ NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u
IoctlGetVaRegions params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
- params.buf_size);
-
- params.buf_size = 0x30;
+ LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
+ params.buf_size);
- params.small = IoctlVaRegion{
- .offset = 0x04000000,
- .page_size = 0x1000,
- .pages = 0x3fbfff,
- };
+ std::scoped_lock lock(mutex);
- params.big = IoctlVaRegion{
- .offset = 0x04000000,
- .page_size = big_page_size,
- .pages = 0x1bffff,
- };
+ if (!vm.initialised) {
+ return NvResult::BadValue;
+ }
- // TODO(ogniK): This probably can stay stubbed but should add support way way later
+ GetVARegionsImpl(params);
std::memcpy(output.data(), &params, output.size());
- std::memcpy(inline_output.data(), &params.small, sizeof(IoctlVaRegion));
- std::memcpy(inline_output.data() + sizeof(IoctlVaRegion), &params.big, sizeof(IoctlVaRegion));
+ std::memcpy(inline_output.data(), &params.regions[0], sizeof(VaRegion));
+ std::memcpy(inline_output.data() + sizeof(VaRegion), &params.regions[1], sizeof(VaRegion));
return NvResult::Success;
}
-std::optional<nvhost_as_gpu::BufferMap> nvhost_as_gpu::FindBufferMap(GPUVAddr gpu_addr) const {
- const auto end{buffer_mappings.upper_bound(gpu_addr)};
- for (auto iter{buffer_mappings.begin()}; iter != end; ++iter) {
- if (gpu_addr >= iter->second.StartAddr() && gpu_addr < iter->second.EndAddr()) {
- return iter->second;
- }
- }
-
- return std::nullopt;
-}
-
-void nvhost_as_gpu::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr,
- bool is_allocated) {
- buffer_mappings[gpu_addr] = {gpu_addr, size, cpu_addr, is_allocated};
-}
-
-std::optional<std::size_t> nvhost_as_gpu::RemoveBufferMap(GPUVAddr gpu_addr) {
- if (const auto iter{buffer_mappings.find(gpu_addr)}; iter != buffer_mappings.end()) {
- std::size_t size{};
-
- if (iter->second.IsAllocated()) {
- size = iter->second.Size();
- }
-
- buffer_mappings.erase(iter);
-
- return size;
- }
-
- return std::nullopt;
+Kernel::KEvent* nvhost_as_gpu::QueryEvent(u32 event_id) {
+ LOG_CRITICAL(Service_NVDRV, "Unknown AS GPU Event {}", event_id);
+ return nullptr;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
index 24e3151cb..86fe71c75 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
@@ -1,36 +1,50 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
+#include <bit>
+#include <list>
#include <map>
#include <memory>
+#include <mutex>
#include <optional>
#include <vector>
+#include "common/address_space.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
-namespace Service::Nvidia::Devices {
+namespace Tegra {
+class MemoryManager;
+} // namespace Tegra
+
+namespace Service::Nvidia {
+class Module;
+}
-constexpr u32 DEFAULT_BIG_PAGE_SIZE = 1 << 16;
-constexpr u32 DEFAULT_SMALL_PAGE_SIZE = 1 << 12;
+namespace Service::Nvidia::NvCore {
+class Container;
+class NvMap;
+} // namespace Service::Nvidia::NvCore
-class nvmap;
+namespace Service::Nvidia::Devices {
-enum class AddressSpaceFlags : u32 {
- None = 0x0,
- FixedOffset = 0x1,
- Remap = 0x100,
+enum class MappingFlags : u32 {
+ None = 0,
+ Fixed = 1 << 0,
+ Sparse = 1 << 1,
+ Remap = 1 << 8,
};
-DECLARE_ENUM_FLAG_OPERATORS(AddressSpaceFlags);
+DECLARE_ENUM_FLAG_OPERATORS(MappingFlags);
class nvhost_as_gpu final : public nvdevice {
public:
- explicit nvhost_as_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_);
+ explicit nvhost_as_gpu(Core::System& system_, Module& module, NvCore::Container& core);
~nvhost_as_gpu() override;
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -43,46 +57,17 @@ public:
void OnOpen(DeviceFD fd) override;
void OnClose(DeviceFD fd) override;
-private:
- class BufferMap final {
- public:
- constexpr BufferMap() = default;
-
- constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_)
- : start_addr{start_addr_}, end_addr{start_addr_ + size_} {}
-
- constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_, VAddr cpu_addr_,
- bool is_allocated_)
- : start_addr{start_addr_}, end_addr{start_addr_ + size_}, cpu_addr{cpu_addr_},
- is_allocated{is_allocated_} {}
-
- constexpr VAddr StartAddr() const {
- return start_addr;
- }
-
- constexpr VAddr EndAddr() const {
- return end_addr;
- }
-
- constexpr std::size_t Size() const {
- return end_addr - start_addr;
- }
-
- constexpr VAddr CpuAddr() const {
- return cpu_addr;
- }
-
- constexpr bool IsAllocated() const {
- return is_allocated;
- }
-
- private:
- GPUVAddr start_addr{};
- GPUVAddr end_addr{};
- VAddr cpu_addr{};
- bool is_allocated{};
+ Kernel::KEvent* QueryEvent(u32 event_id) override;
+
+ struct VaRegion {
+ u64 offset;
+ u32 page_size;
+ u32 _pad0_;
+ u64 pages;
};
+ static_assert(sizeof(VaRegion) == 0x18);
+private:
struct IoctlAllocAsEx {
u32_le flags{}; // usually passes 1
s32_le as_fd{}; // ignored; passes 0
@@ -97,7 +82,7 @@ private:
struct IoctlAllocSpace {
u32_le pages{};
u32_le page_size{};
- AddressSpaceFlags flags{};
+ MappingFlags flags{};
INSERT_PADDING_WORDS(1);
union {
u64_le offset;
@@ -114,19 +99,19 @@ private:
static_assert(sizeof(IoctlFreeSpace) == 16, "IoctlFreeSpace is incorrect size");
struct IoctlRemapEntry {
- u16_le flags{};
- u16_le kind{};
- u32_le nvmap_handle{};
- u32_le map_offset{};
- u32_le offset{};
- u32_le pages{};
+ u16 flags;
+ u16 kind;
+ NvCore::NvMap::Handle::Id handle;
+ u32 handle_offset_big_pages;
+ u32 as_offset_big_pages;
+ u32 big_pages;
};
static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
struct IoctlMapBufferEx {
- AddressSpaceFlags flags{}; // bit0: fixed_offset, bit2: cacheable
- u32_le kind{}; // -1 is default
- u32_le nvmap_handle{};
+ MappingFlags flags{}; // bit0: fixed_offset, bit2: cacheable
+ u32_le kind{}; // -1 is default
+ NvCore::NvMap::Handle::Id handle;
u32_le page_size{}; // 0 means don't care
s64_le buffer_offset{};
u64_le mapping_size{};
@@ -144,27 +129,15 @@ private:
};
static_assert(sizeof(IoctlBindChannel) == 4, "IoctlBindChannel is incorrect size");
- struct IoctlVaRegion {
- u64_le offset{};
- u32_le page_size{};
- INSERT_PADDING_WORDS(1);
- u64_le pages{};
- };
- static_assert(sizeof(IoctlVaRegion) == 24, "IoctlVaRegion is incorrect size");
-
struct IoctlGetVaRegions {
u64_le buf_addr{}; // (contained output user ptr on linux, ignored)
u32_le buf_size{}; // forced to 2*sizeof(struct va_region)
u32_le reserved{};
- IoctlVaRegion small{};
- IoctlVaRegion big{};
+ std::array<VaRegion, 2> regions{};
};
- static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2,
+ static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(VaRegion) * 2,
"IoctlGetVaRegions is incorrect size");
- s32 channel{};
- u32 big_page_size{DEFAULT_BIG_PAGE_SIZE};
-
NvResult AllocAsEx(const std::vector<u8>& input, std::vector<u8>& output);
NvResult AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output);
NvResult Remap(const std::vector<u8>& input, std::vector<u8>& output);
@@ -173,18 +146,75 @@ private:
NvResult FreeSpace(const std::vector<u8>& input, std::vector<u8>& output);
NvResult BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
+ void GetVARegionsImpl(IoctlGetVaRegions& params);
NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output);
- std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const;
- void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated);
- std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr);
+ void FreeMappingLocked(u64 offset);
+
+ Module& module;
+
+ NvCore::Container& container;
+ NvCore::NvMap& nvmap;
- std::shared_ptr<nvmap> nvmap_dev;
+ struct Mapping {
+ VAddr ptr;
+ u64 offset;
+ u64 size;
+ bool fixed;
+ bool big_page; // Only valid if fixed == false
+ bool sparse_alloc;
+
+ Mapping(VAddr ptr_, u64 offset_, u64 size_, bool fixed_, bool big_page_, bool sparse_alloc_)
+ : ptr(ptr_), offset(offset_), size(size_), fixed(fixed_), big_page(big_page_),
+ sparse_alloc(sparse_alloc_) {}
+ };
+
+ struct Allocation {
+ u64 size;
+ std::list<std::shared_ptr<Mapping>> mappings;
+ u32 page_size;
+ bool sparse;
+ bool big_pages;
+ };
- // This is expected to be ordered, therefore we must use a map, not unordered_map
- std::map<GPUVAddr, BufferMap> buffer_mappings;
+ std::map<u64, std::shared_ptr<Mapping>>
+ mapping_map; //!< This maps the base addresses of mapped buffers to their total sizes and
+ //!< mapping type, this is needed as what was originally a single buffer may
+ //!< have been split into multiple GPU side buffers with the remap flag.
+ std::map<u64, Allocation> allocation_map; //!< Holds allocations created by AllocSpace from
+ //!< which fixed buffers can be mapped into
+ std::mutex mutex; //!< Locks all AS operations
+
+ struct VM {
+ static constexpr u32 YUZU_PAGESIZE{0x1000};
+ static constexpr u32 PAGE_SIZE_BITS{std::countr_zero(YUZU_PAGESIZE)};
+
+ static constexpr u32 SUPPORTED_BIG_PAGE_SIZES{0x30000};
+ static constexpr u32 DEFAULT_BIG_PAGE_SIZE{0x20000};
+ u32 big_page_size{DEFAULT_BIG_PAGE_SIZE};
+ u32 big_page_size_bits{std::countr_zero(DEFAULT_BIG_PAGE_SIZE)};
+
+ static constexpr u32 VA_START_SHIFT{10};
+ static constexpr u64 DEFAULT_VA_SPLIT{1ULL << 34};
+ static constexpr u64 DEFAULT_VA_RANGE{1ULL << 37};
+ u64 va_range_start{DEFAULT_BIG_PAGE_SIZE << VA_START_SHIFT};
+ u64 va_range_split{DEFAULT_VA_SPLIT};
+ u64 va_range_end{DEFAULT_VA_RANGE};
+
+ using Allocator = Common::FlatAllocator<u32, 0, 32>;
+
+ std::unique_ptr<Allocator> big_page_allocator;
+ std::shared_ptr<Allocator>
+ small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
+
+ bool initialised{};
+ } vm;
+ std::shared_ptr<Tegra::MemoryManager> gmmu;
+
+ // s32 channel{};
+ // u32 big_page_size{VM::DEFAULT_BIG_PAGE_SIZE};
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
index f9b82b504..5bee4a3d3 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -1,25 +1,39 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+#include <bit>
#include <cstdlib>
#include <cstring>
+#include <fmt/format.h>
#include "common/assert.h"
#include "common/logging/log.h"
+#include "common/scope_exit.h"
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_writable_event.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
#include "video_core/gpu.h"
+#include "video_core/host1x/host1x.h"
namespace Service::Nvidia::Devices {
nvhost_ctrl::nvhost_ctrl(Core::System& system_, EventInterface& events_interface_,
- SyncpointManager& syncpoint_manager_)
- : nvdevice{system_}, events_interface{events_interface_}, syncpoint_manager{
- syncpoint_manager_} {}
-nvhost_ctrl::~nvhost_ctrl() = default;
+ NvCore::Container& core_)
+ : nvdevice{system_}, events_interface{events_interface_}, core{core_},
+ syncpoint_manager{core_.GetSyncpointManager()} {}
+
+nvhost_ctrl::~nvhost_ctrl() {
+ for (auto& event : events) {
+ if (!event.registered) {
+ continue;
+ }
+ events_interface.FreeEvent(event.kevent);
+ }
+}
NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
std::vector<u8>& output) {
@@ -31,13 +45,15 @@ NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>&
case 0x1c:
return IocCtrlClearEventWait(input, output);
case 0x1d:
- return IocCtrlEventWait(input, output, false);
- case 0x1e:
return IocCtrlEventWait(input, output, true);
+ case 0x1e:
+ return IocCtrlEventWait(input, output, false);
case 0x1f:
return IocCtrlEventRegister(input, output);
case 0x20:
return IocCtrlEventUnregister(input, output);
+ case 0x21:
+ return IocCtrlEventUnregisterBatch(input, output);
}
break;
default:
@@ -61,6 +77,7 @@ NvResult nvhost_ctrl::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>&
}
void nvhost_ctrl::OnOpen(DeviceFD fd) {}
+
void nvhost_ctrl::OnClose(DeviceFD fd) {}
NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -72,116 +89,167 @@ NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector
}
NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
- bool is_async) {
+ bool is_allocation) {
IocCtrlEventWaitParams params{};
std::memcpy(&params, input.data(), sizeof(params));
- LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_async={}",
- params.syncpt_id, params.threshold, params.timeout, is_async);
+ LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_allocation={}",
+ params.fence.id, params.fence.value, params.timeout, is_allocation);
- if (params.syncpt_id >= MaxSyncPoints) {
- return NvResult::BadParameter;
- }
+ bool must_unmark_fail = !is_allocation;
+ const u32 event_id = params.value.raw;
+ SCOPE_EXIT({
+ std::memcpy(output.data(), &params, sizeof(params));
+ if (must_unmark_fail) {
+ events[event_id].fails = 0;
+ }
+ });
- u32 event_id = params.value & 0x00FF;
+ const u32 fence_id = static_cast<u32>(params.fence.id);
- if (event_id >= MaxNvEvents) {
- std::memcpy(output.data(), &params, sizeof(params));
+ if (fence_id >= MaxSyncPoints) {
return NvResult::BadParameter;
}
- if (syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) {
- params.value = syncpoint_manager.GetSyncpointMin(params.syncpt_id);
- std::memcpy(output.data(), &params, sizeof(params));
- events_interface.failed[event_id] = false;
+ if (params.fence.value == 0) {
+ if (!syncpoint_manager.IsSyncpointAllocated(params.fence.id)) {
+ LOG_WARNING(Service_NVDRV,
+ "Unallocated syncpt_id={}, threshold={}, timeout={}, is_allocation={}",
+ params.fence.id, params.fence.value, params.timeout, is_allocation);
+ } else {
+ params.value.raw = syncpoint_manager.ReadSyncpointMinValue(fence_id);
+ }
return NvResult::Success;
}
- if (const auto new_value = syncpoint_manager.RefreshSyncpoint(params.syncpt_id);
- syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) {
- params.value = new_value;
- std::memcpy(output.data(), &params, sizeof(params));
- events_interface.failed[event_id] = false;
+ if (syncpoint_manager.IsFenceSignalled(params.fence)) {
+ params.value.raw = syncpoint_manager.ReadSyncpointMinValue(fence_id);
return NvResult::Success;
}
- auto& event = events_interface.events[event_id];
- auto& gpu = system.GPU();
-
- // This is mostly to take into account unimplemented features. As synced
- // gpu is always synced.
- if (!gpu.IsAsync()) {
- event.event->GetWritableEvent().Signal();
- return NvResult::Success;
- }
- const u32 current_syncpoint_value = event.fence.value;
- const s32 diff = current_syncpoint_value - params.threshold;
- if (diff >= 0) {
- event.event->GetWritableEvent().Signal();
- params.value = current_syncpoint_value;
- std::memcpy(output.data(), &params, sizeof(params));
- events_interface.failed[event_id] = false;
+ if (const auto new_value = syncpoint_manager.UpdateMin(fence_id);
+ syncpoint_manager.IsFenceSignalled(params.fence)) {
+ params.value.raw = new_value;
return NvResult::Success;
}
- const u32 target_value = current_syncpoint_value - diff;
- if (!is_async) {
- params.value = 0;
+ auto& host1x_syncpoint_manager = system.Host1x().GetSyncpointManager();
+ const u32 target_value = params.fence.value;
+
+ auto lock = NvEventsLock();
+
+ u32 slot = [&]() {
+ if (is_allocation) {
+ params.value.raw = 0;
+ return FindFreeNvEvent(fence_id);
+ } else {
+ return params.value.raw;
+ }
+ }();
+
+ must_unmark_fail = false;
+
+ const auto check_failing = [&]() {
+ if (events[slot].fails > 2) {
+ {
+ auto lk = system.StallProcesses();
+ host1x_syncpoint_manager.WaitHost(fence_id, target_value);
+ system.UnstallProcesses();
+ }
+ params.value.raw = target_value;
+ return true;
+ }
+ return false;
+ };
+
+ if (slot >= MaxNvEvents) {
+ return NvResult::BadParameter;
}
if (params.timeout == 0) {
- std::memcpy(output.data(), &params, sizeof(params));
+ if (check_failing()) {
+ events[slot].fails = 0;
+ return NvResult::Success;
+ }
return NvResult::Timeout;
}
- EventState status = events_interface.status[event_id];
- const bool bad_parameter = status != EventState::Free && status != EventState::Registered;
- if (bad_parameter) {
- std::memcpy(output.data(), &params, sizeof(params));
+ auto& event = events[slot];
+
+ if (!event.registered) {
return NvResult::BadParameter;
}
- events_interface.SetEventStatus(event_id, EventState::Waiting);
- events_interface.assigned_syncpt[event_id] = params.syncpt_id;
- events_interface.assigned_value[event_id] = target_value;
- if (is_async) {
- params.value = params.syncpt_id << 4;
- } else {
- params.value = ((params.syncpt_id & 0xfff) << 16) | 0x10000000;
- }
- params.value |= event_id;
- event.event->GetWritableEvent().Clear();
- if (events_interface.failed[event_id]) {
- {
- auto lk = system.StallCPU();
- gpu.WaitFence(params.syncpt_id, target_value);
- system.UnstallCPU();
- }
- std::memcpy(output.data(), &params, sizeof(params));
- events_interface.failed[event_id] = false;
+
+ if (event.IsBeingUsed()) {
+ return NvResult::BadParameter;
+ }
+
+ if (check_failing()) {
+ event.fails = 0;
return NvResult::Success;
}
- gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value);
- std::memcpy(output.data(), &params, sizeof(params));
+
+ params.value.raw = 0;
+
+ event.status.store(EventState::Waiting, std::memory_order_release);
+ event.assigned_syncpt = fence_id;
+ event.assigned_value = target_value;
+ if (is_allocation) {
+ params.value.syncpoint_id_for_allocation.Assign(static_cast<u16>(fence_id));
+ params.value.event_allocated.Assign(1);
+ } else {
+ params.value.syncpoint_id.Assign(fence_id);
+ }
+ params.value.raw |= slot;
+
+ event.wait_handle =
+ host1x_syncpoint_manager.RegisterHostAction(fence_id, target_value, [this, slot]() {
+ auto& event_ = events[slot];
+ if (event_.status.exchange(EventState::Signalling, std::memory_order_acq_rel) ==
+ EventState::Waiting) {
+ event_.kevent->GetWritableEvent().Signal();
+ }
+ event_.status.store(EventState::Signalled, std::memory_order_release);
+ });
return NvResult::Timeout;
}
+NvResult nvhost_ctrl::FreeEvent(u32 slot) {
+ if (slot >= MaxNvEvents) {
+ return NvResult::BadParameter;
+ }
+
+ auto& event = events[slot];
+
+ if (!event.registered) {
+ return NvResult::Success;
+ }
+
+ if (event.IsBeingUsed()) {
+ return NvResult::Busy;
+ }
+
+ FreeNvEvent(slot);
+ return NvResult::Success;
+}
+
NvResult nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
IocCtrlEventRegisterParams params{};
std::memcpy(&params, input.data(), sizeof(params));
- const u32 event_id = params.user_event_id & 0x00FF;
+ const u32 event_id = params.user_event_id;
LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
if (event_id >= MaxNvEvents) {
return NvResult::BadParameter;
}
- if (events_interface.registered[event_id]) {
- const auto event_state = events_interface.status[event_id];
- if (event_state != EventState::Free) {
- LOG_WARNING(Service_NVDRV, "Event already registered! Unregistering previous event");
- events_interface.UnregisterEvent(event_id);
- } else {
- return NvResult::BadParameter;
+
+ auto lock = NvEventsLock();
+
+ if (events[event_id].registered) {
+ const auto result = FreeEvent(event_id);
+ if (result != NvResult::Success) {
+ return result;
}
}
- events_interface.RegisterEvent(event_id);
+ CreateNvEvent(event_id);
return NvResult::Success;
}
@@ -191,34 +259,142 @@ NvResult nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input,
std::memcpy(&params, input.data(), sizeof(params));
const u32 event_id = params.user_event_id & 0x00FF;
LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
- if (event_id >= MaxNvEvents) {
- return NvResult::BadParameter;
- }
- if (!events_interface.registered[event_id]) {
- return NvResult::BadParameter;
+
+ auto lock = NvEventsLock();
+ return FreeEvent(event_id);
+}
+
+NvResult nvhost_ctrl::IocCtrlEventUnregisterBatch(const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ IocCtrlEventUnregisterBatchParams params{};
+ std::memcpy(&params, input.data(), sizeof(params));
+ u64 event_mask = params.user_events;
+ LOG_DEBUG(Service_NVDRV, " called, event_mask: {:X}", event_mask);
+
+ auto lock = NvEventsLock();
+ while (event_mask != 0) {
+ const u64 event_id = std::countr_zero(event_mask);
+ event_mask &= ~(1ULL << event_id);
+ const auto result = FreeEvent(static_cast<u32>(event_id));
+ if (result != NvResult::Success) {
+ return result;
+ }
}
- events_interface.UnregisterEvent(event_id);
return NvResult::Success;
}
NvResult nvhost_ctrl::IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output) {
- IocCtrlEventSignalParams params{};
+ IocCtrlEventClearParams params{};
std::memcpy(&params, input.data(), sizeof(params));
- u32 event_id = params.event_id & 0x00FF;
- LOG_WARNING(Service_NVDRV, "cleared event wait on, event_id: {:X}", event_id);
+ u32 event_id = params.event_id.slot;
+ LOG_DEBUG(Service_NVDRV, "called, event_id: {:X}", event_id);
if (event_id >= MaxNvEvents) {
return NvResult::BadParameter;
}
- if (events_interface.status[event_id] == EventState::Waiting) {
- events_interface.LiberateEvent(event_id);
- }
- events_interface.failed[event_id] = true;
- syncpoint_manager.RefreshSyncpoint(events_interface.events[event_id].fence.id);
+ auto lock = NvEventsLock();
+
+ auto& event = events[event_id];
+ if (event.status.exchange(EventState::Cancelling, std::memory_order_acq_rel) ==
+ EventState::Waiting) {
+ auto& host1x_syncpoint_manager = system.Host1x().GetSyncpointManager();
+ host1x_syncpoint_manager.DeregisterHostAction(event.assigned_syncpt, event.wait_handle);
+ syncpoint_manager.UpdateMin(event.assigned_syncpt);
+ event.wait_handle = {};
+ }
+ event.fails++;
+ event.status.store(EventState::Cancelled, std::memory_order_release);
+ event.kevent->GetWritableEvent().Clear();
return NvResult::Success;
}
+Kernel::KEvent* nvhost_ctrl::QueryEvent(u32 event_id) {
+ const auto desired_event = SyncpointEventValue{.raw = event_id};
+
+ const bool allocated = desired_event.event_allocated.Value() != 0;
+ const u32 slot{allocated ? desired_event.partial_slot.Value()
+ : static_cast<u32>(desired_event.slot)};
+ if (slot >= MaxNvEvents) {
+ ASSERT(false);
+ return nullptr;
+ }
+
+ const u32 syncpoint_id{allocated ? desired_event.syncpoint_id_for_allocation.Value()
+ : desired_event.syncpoint_id.Value()};
+
+ auto lock = NvEventsLock();
+
+ auto& event = events[slot];
+ if (event.registered && event.assigned_syncpt == syncpoint_id) {
+ ASSERT(event.kevent);
+ return event.kevent;
+ }
+ // Is this possible in hardware?
+ ASSERT_MSG(false, "Slot:{}, SyncpointID:{}, requested", slot, syncpoint_id);
+ return nullptr;
+}
+
+std::unique_lock<std::mutex> nvhost_ctrl::NvEventsLock() {
+ return std::unique_lock<std::mutex>(events_mutex);
+}
+
+void nvhost_ctrl::CreateNvEvent(u32 event_id) {
+ auto& event = events[event_id];
+ ASSERT(!event.kevent);
+ ASSERT(!event.registered);
+ ASSERT(!event.IsBeingUsed());
+ event.kevent = events_interface.CreateEvent(fmt::format("NVCTRL::NvEvent_{}", event_id));
+ event.status = EventState::Available;
+ event.registered = true;
+ const u64 mask = 1ULL << event_id;
+ event.fails = 0;
+ events_mask |= mask;
+ event.assigned_syncpt = 0;
+}
+
+void nvhost_ctrl::FreeNvEvent(u32 event_id) {
+ auto& event = events[event_id];
+ ASSERT(event.kevent);
+ ASSERT(event.registered);
+ ASSERT(!event.IsBeingUsed());
+ events_interface.FreeEvent(event.kevent);
+ event.kevent = nullptr;
+ event.status = EventState::Available;
+ event.registered = false;
+ const u64 mask = ~(1ULL << event_id);
+ events_mask &= mask;
+}
+
+u32 nvhost_ctrl::FindFreeNvEvent(u32 syncpoint_id) {
+ u32 slot{MaxNvEvents};
+ u32 free_slot{MaxNvEvents};
+ for (u32 i = 0; i < MaxNvEvents; i++) {
+ auto& event = events[i];
+ if (event.registered) {
+ if (!event.IsBeingUsed()) {
+ slot = i;
+ if (event.assigned_syncpt == syncpoint_id) {
+ return slot;
+ }
+ }
+ } else if (free_slot == MaxNvEvents) {
+ free_slot = i;
+ }
+ }
+ if (free_slot < MaxNvEvents) {
+ CreateNvEvent(free_slot);
+ return free_slot;
+ }
+
+ if (slot < MaxNvEvents) {
+ return slot;
+ }
+
+ LOG_CRITICAL(Service_NVDRV, "Failed to allocate an event");
+ return 0;
+}
+
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
index cdf03887d..0b56d7070 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
@@ -1,21 +1,28 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <vector>
+#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/nvdrv.h"
+#include "video_core/host1x/syncpoint_manager.h"
+
+namespace Service::Nvidia::NvCore {
+class Container;
+class SyncpointManager;
+} // namespace Service::Nvidia::NvCore
namespace Service::Nvidia::Devices {
class nvhost_ctrl final : public nvdevice {
public:
explicit nvhost_ctrl(Core::System& system_, EventInterface& events_interface_,
- SyncpointManager& syncpoint_manager_);
+ NvCore::Container& core);
~nvhost_ctrl() override;
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -28,7 +35,70 @@ public:
void OnOpen(DeviceFD fd) override;
void OnClose(DeviceFD fd) override;
+ Kernel::KEvent* QueryEvent(u32 event_id) override;
+
+ union SyncpointEventValue {
+ u32 raw;
+
+ union {
+ BitField<0, 4, u32> partial_slot;
+ BitField<4, 28, u32> syncpoint_id;
+ };
+
+ struct {
+ u16 slot;
+ union {
+ BitField<0, 12, u16> syncpoint_id_for_allocation;
+ BitField<12, 1, u16> event_allocated;
+ };
+ };
+ };
+ static_assert(sizeof(SyncpointEventValue) == sizeof(u32));
+
private:
+ struct InternalEvent {
+ // Mask representing registered events
+
+ // Each kernel event associated to an NV event
+ Kernel::KEvent* kevent{};
+ // The status of the current NVEvent
+ std::atomic<EventState> status{};
+
+ // Tells the NVEvent that it has failed.
+ u32 fails{};
+ // When an NVEvent is waiting on GPU interrupt, this is the sync_point
+ // associated with it.
+ u32 assigned_syncpt{};
+ // This is the value of the GPU interrupt for which the NVEvent is waiting
+ // for.
+ u32 assigned_value{};
+
+ // Tells if an NVEvent is registered or not
+ bool registered{};
+
+ // Used for waiting on a syncpoint & canceling it.
+ Tegra::Host1x::SyncpointManager::ActionHandle wait_handle{};
+
+ bool IsBeingUsed() const {
+ const auto current_status = status.load(std::memory_order_acquire);
+ return current_status == EventState::Waiting ||
+ current_status == EventState::Cancelling ||
+ current_status == EventState::Signalling;
+ }
+ };
+
+ std::unique_lock<std::mutex> NvEventsLock();
+
+ void CreateNvEvent(u32 event_id);
+
+ void FreeNvEvent(u32 event_id);
+
+ u32 FindFreeNvEvent(u32 syncpoint_id);
+
+ std::array<InternalEvent, MaxNvEvents> events{};
+ std::mutex events_mutex;
+ u64 events_mask{};
+
struct IocSyncptReadParams {
u32_le id{};
u32_le value{};
@@ -84,27 +154,18 @@ private:
};
static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size");
- struct IocCtrlEventSignalParams {
- u32_le event_id{};
+ struct IocCtrlEventClearParams {
+ SyncpointEventValue event_id{};
};
- static_assert(sizeof(IocCtrlEventSignalParams) == 4,
- "IocCtrlEventSignalParams is incorrect size");
+ static_assert(sizeof(IocCtrlEventClearParams) == 4,
+ "IocCtrlEventClearParams is incorrect size");
struct IocCtrlEventWaitParams {
- u32_le syncpt_id{};
- u32_le threshold{};
- s32_le timeout{};
- u32_le value{};
- };
- static_assert(sizeof(IocCtrlEventWaitParams) == 16, "IocCtrlEventWaitParams is incorrect size");
-
- struct IocCtrlEventWaitAsyncParams {
- u32_le syncpt_id{};
- u32_le threshold{};
+ NvFence fence{};
u32_le timeout{};
- u32_le value{};
+ SyncpointEventValue value{};
};
- static_assert(sizeof(IocCtrlEventWaitAsyncParams) == 16,
+ static_assert(sizeof(IocCtrlEventWaitParams) == 16,
"IocCtrlEventWaitAsyncParams is incorrect size");
struct IocCtrlEventRegisterParams {
@@ -119,19 +180,25 @@ private:
static_assert(sizeof(IocCtrlEventUnregisterParams) == 4,
"IocCtrlEventUnregisterParams is incorrect size");
- struct IocCtrlEventKill {
+ struct IocCtrlEventUnregisterBatchParams {
u64_le user_events{};
};
- static_assert(sizeof(IocCtrlEventKill) == 8, "IocCtrlEventKill is incorrect size");
+ static_assert(sizeof(IocCtrlEventUnregisterBatchParams) == 8,
+ "IocCtrlEventKill is incorrect size");
NvResult NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
- NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async);
+ NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
+ bool is_allocation);
NvResult IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocCtrlEventUnregisterBatch(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult FreeEvent(u32 slot);
+
EventInterface& events_interface;
- SyncpointManager& syncpoint_manager;
+ NvCore::Container& core;
+ NvCore::SyncpointManager& syncpoint_manager;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
index 05b4e2151..ced57dfe6 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/assert.h"
@@ -8,11 +7,19 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
namespace Service::Nvidia::Devices {
-nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system_) : nvdevice{system_} {}
-nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default;
+nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system_, EventInterface& events_interface_)
+ : nvdevice{system_}, events_interface{events_interface_} {
+ error_notifier_event = events_interface.CreateEvent("CtrlGpuErrorNotifier");
+ unknown_event = events_interface.CreateEvent("CtrlGpuUknownEvent");
+}
+nvhost_ctrl_gpu::~nvhost_ctrl_gpu() {
+ events_interface.FreeEvent(error_notifier_event);
+ events_interface.FreeEvent(unknown_event);
+}
NvResult nvhost_ctrl_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
std::vector<u8>& output) {
@@ -287,4 +294,17 @@ NvResult nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u
return NvResult::Success;
}
+Kernel::KEvent* nvhost_ctrl_gpu::QueryEvent(u32 event_id) {
+ switch (event_id) {
+ case 1:
+ return error_notifier_event;
+ case 2:
+ return unknown_event;
+ default: {
+ LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id);
+ }
+ }
+ return nullptr;
+}
+
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
index 898d00a17..1e8f254e2 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
@@ -1,19 +1,24 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <vector>
+
+#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
+namespace Service::Nvidia {
+class EventInterface;
+}
+
namespace Service::Nvidia::Devices {
class nvhost_ctrl_gpu final : public nvdevice {
public:
- explicit nvhost_ctrl_gpu(Core::System& system_);
+ explicit nvhost_ctrl_gpu(Core::System& system_, EventInterface& events_interface_);
~nvhost_ctrl_gpu() override;
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -26,6 +31,8 @@ public:
void OnOpen(DeviceFD fd) override;
void OnClose(DeviceFD fd) override;
+ Kernel::KEvent* QueryEvent(u32 event_id) override;
+
private:
struct IoctlGpuCharacteristics {
u32_le arch; // 0x120 (NVGPU_GPU_ARCH_GM200)
@@ -159,6 +166,12 @@ private:
NvResult ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output);
NvResult FlushL2(const std::vector<u8>& input, std::vector<u8>& output);
NvResult GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output);
+
+ EventInterface& events_interface;
+
+ // Events
+ Kernel::KEvent* error_notifier_event;
+ Kernel::KEvent* unknown_event;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index 0a043e386..45a759fa8 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -1,34 +1,50 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
-#include "core/hle/service/nvdrv/syncpoint_manager.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/memory.h"
+#include "video_core/control/channel_state.h"
+#include "video_core/engines/puller.h"
#include "video_core/gpu.h"
+#include "video_core/host1x/host1x.h"
namespace Service::Nvidia::Devices {
namespace {
-Tegra::CommandHeader BuildFenceAction(Tegra::GPU::FenceOperation op, u32 syncpoint_id) {
- Tegra::GPU::FenceAction result{};
+Tegra::CommandHeader BuildFenceAction(Tegra::Engines::Puller::FenceOperation op, u32 syncpoint_id) {
+ Tegra::Engines::Puller::FenceAction result{};
result.op.Assign(op);
result.syncpoint_id.Assign(syncpoint_id);
return {result.raw};
}
} // namespace
-nvhost_gpu::nvhost_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_)
- : nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)}, syncpoint_manager{syncpoint_manager_} {
- channel_fence.id = syncpoint_manager_.AllocateSyncpoint();
- channel_fence.value = system_.GPU().GetSyncpointValue(channel_fence.id);
+nvhost_gpu::nvhost_gpu(Core::System& system_, EventInterface& events_interface_,
+ NvCore::Container& core_)
+ : nvdevice{system_}, events_interface{events_interface_}, core{core_},
+ syncpoint_manager{core_.GetSyncpointManager()}, nvmap{core.GetNvMapFile()},
+ channel_state{system.GPU().AllocateChannel()} {
+ channel_syncpoint = syncpoint_manager.AllocateSyncpoint(false);
+ sm_exception_breakpoint_int_report_event =
+ events_interface.CreateEvent("GpuChannelSMExceptionBreakpointInt");
+ sm_exception_breakpoint_pause_report_event =
+ events_interface.CreateEvent("GpuChannelSMExceptionBreakpointPause");
+ error_notifier_event = events_interface.CreateEvent("GpuChannelErrorNotifier");
}
-nvhost_gpu::~nvhost_gpu() = default;
+nvhost_gpu::~nvhost_gpu() {
+ events_interface.FreeEvent(sm_exception_breakpoint_int_report_event);
+ events_interface.FreeEvent(sm_exception_breakpoint_pause_report_event);
+ events_interface.FreeEvent(error_notifier_event);
+ syncpoint_manager.FreeSyncpoint(channel_syncpoint);
+}
NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
std::vector<u8>& output) {
@@ -168,9 +184,14 @@ NvResult nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8
params.num_entries, params.flags, params.unk0, params.unk1, params.unk2,
params.unk3);
- channel_fence.value = system.GPU().GetSyncpointValue(channel_fence.id);
+ if (channel_state->initialized) {
+ LOG_CRITICAL(Service_NVDRV, "Already allocated!");
+ return NvResult::AlreadyAllocated;
+ }
+
+ system.GPU().InitChannel(*channel_state);
- params.fence_out = channel_fence;
+ params.fence_out = syncpoint_manager.GetSyncpointFence(channel_syncpoint);
std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
@@ -187,40 +208,39 @@ 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::BuildCommandHeader(Tegra::BufferMethods::SyncpointPayload, 1,
Tegra::SubmissionMode::Increasing),
{fence.value},
- Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1,
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointOperation, 1,
Tegra::SubmissionMode::Increasing),
- BuildFenceAction(Tegra::GPU::FenceOperation::Acquire, fence.id),
+ BuildFenceAction(Tegra::Engines::Puller::FenceOperation::Acquire, fence.id),
};
}
-static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(Fence fence, u32 add_increment) {
+static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(NvFence fence) {
std::vector<Tegra::CommandHeader> result{
- Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1,
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointPayload, 1,
Tegra::SubmissionMode::Increasing),
{}};
- for (u32 count = 0; count < add_increment; ++count) {
- result.emplace_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1,
+ for (u32 count = 0; count < 2; ++count) {
+ result.emplace_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointOperation, 1,
Tegra::SubmissionMode::Increasing));
- result.emplace_back(BuildFenceAction(Tegra::GPU::FenceOperation::Increment, fence.id));
+ result.emplace_back(
+ BuildFenceAction(Tegra::Engines::Puller::FenceOperation::Increment, fence.id));
}
return result;
}
-static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(Fence fence,
- u32 add_increment) {
+static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(NvFence fence) {
std::vector<Tegra::CommandHeader> result{
- Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForInterrupt, 1,
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForIdle, 1,
Tegra::SubmissionMode::Increasing),
{}};
- const std::vector<Tegra::CommandHeader> increment{
- BuildIncrementCommandList(fence, add_increment)};
+ const std::vector<Tegra::CommandHeader> increment{BuildIncrementCommandList(fence)};
result.insert(result.end(), increment.begin(), increment.end());
@@ -234,33 +254,41 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector<u8>
auto& gpu = system.GPU();
- params.fence_out.id = channel_fence.id;
+ std::scoped_lock lock(channel_mutex);
- if (params.flags.add_wait.Value() &&
- !syncpoint_manager.IsSyncpointExpired(params.fence_out.id, params.fence_out.value)) {
- gpu.PushGPUEntries(Tegra::CommandList{BuildWaitCommandList(params.fence_out)});
- }
+ const auto bind_id = channel_state->bind_id;
- if (params.flags.add_increment.Value() || params.flags.increment.Value()) {
- const u32 increment_value = params.flags.increment.Value() ? params.fence_out.value : 0;
- params.fence_out.value = syncpoint_manager.IncreaseSyncpoint(
- params.fence_out.id, params.AddIncrementValue() + increment_value);
- } else {
- params.fence_out.value = syncpoint_manager.GetSyncpointMax(params.fence_out.id);
+ auto& flags = params.flags;
+
+ if (flags.fence_wait.Value()) {
+ if (flags.increment_value.Value()) {
+ return NvResult::BadParameter;
+ }
+
+ if (!syncpoint_manager.IsFenceSignalled(params.fence)) {
+ gpu.PushGPUEntries(bind_id, Tegra::CommandList{BuildWaitCommandList(params.fence)});
+ }
}
- gpu.PushGPUEntries(std::move(entries));
+ params.fence.id = channel_syncpoint;
+
+ u32 increment{(flags.fence_increment.Value() != 0 ? 2 : 0) +
+ (flags.increment_value.Value() != 0 ? params.fence.value : 0)};
+ params.fence.value = syncpoint_manager.IncrementSyncpointMaxExt(channel_syncpoint, increment);
+ gpu.PushGPUEntries(bind_id, std::move(entries));
- if (params.flags.add_increment.Value()) {
- if (params.flags.suppress_wfi) {
- gpu.PushGPUEntries(Tegra::CommandList{
- BuildIncrementCommandList(params.fence_out, params.AddIncrementValue())});
+ if (flags.fence_increment.Value()) {
+ if (flags.suppress_wfi.Value()) {
+ gpu.PushGPUEntries(bind_id,
+ Tegra::CommandList{BuildIncrementCommandList(params.fence)});
} else {
- gpu.PushGPUEntries(Tegra::CommandList{
- BuildIncrementWithWfiCommandList(params.fence_out, params.AddIncrementValue())});
+ gpu.PushGPUEntries(bind_id,
+ Tegra::CommandList{BuildIncrementWithWfiCommandList(params.fence)});
}
}
+ flags.raw = 0;
+
std::memcpy(output.data(), &params, sizeof(IoctlSubmitGpfifo));
return NvResult::Success;
}
@@ -328,4 +356,19 @@ NvResult nvhost_gpu::ChannelSetTimeslice(const std::vector<u8>& input, std::vect
return NvResult::Success;
}
+Kernel::KEvent* nvhost_gpu::QueryEvent(u32 event_id) {
+ switch (event_id) {
+ case 1:
+ return sm_exception_breakpoint_int_report_event;
+ case 2:
+ return sm_exception_breakpoint_pause_report_event;
+ case 3:
+ return error_notifier_event;
+ default: {
+ LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id);
+ }
+ }
+ return nullptr;
+}
+
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index f27a82bff..1e4ecd55b 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -1,29 +1,43 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <vector>
#include "common/bit_field.h"
+#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "video_core/dma_pusher.h"
+namespace Tegra {
+namespace Control {
+struct ChannelState;
+}
+} // namespace Tegra
+
namespace Service::Nvidia {
+
+namespace NvCore {
+class Container;
+class NvMap;
class SyncpointManager;
-}
+} // namespace NvCore
+
+class EventInterface;
+} // namespace Service::Nvidia
namespace Service::Nvidia::Devices {
+class nvhost_as_gpu;
class nvmap;
class nvhost_gpu final : public nvdevice {
public:
- explicit nvhost_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_);
+ explicit nvhost_gpu(Core::System& system_, EventInterface& events_interface_,
+ NvCore::Container& core);
~nvhost_gpu() override;
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -36,7 +50,10 @@ public:
void OnOpen(DeviceFD fd) override;
void OnClose(DeviceFD fd) override;
+ Kernel::KEvent* QueryEvent(u32 event_id) override;
+
private:
+ friend class nvhost_as_gpu;
enum class CtxObjects : u32_le {
Ctx2D = 0x902D,
Ctx3D = 0xB197,
@@ -108,7 +125,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{};
@@ -126,7 +143,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
@@ -146,19 +163,15 @@ private:
u32_le num_entries{}; // number of fence objects being submitted
union {
u32_le raw;
- BitField<0, 1, u32_le> add_wait; // append a wait sync_point to the list
- BitField<1, 1, u32_le> add_increment; // append an increment to the list
- BitField<2, 1, u32_le> new_hw_format; // mostly ignored
- BitField<4, 1, u32_le> suppress_wfi; // suppress wait for interrupt
- BitField<8, 1, u32_le> increment; // increment the returned fence
+ BitField<0, 1, u32_le> fence_wait; // append a wait sync_point to the list
+ BitField<1, 1, u32_le> fence_increment; // append an increment to the list
+ BitField<2, 1, u32_le> new_hw_format; // mostly ignored
+ BitField<4, 1, u32_le> suppress_wfi; // suppress wait for interrupt
+ BitField<8, 1, u32_le> increment_value; // increment the returned fence
} flags;
- Fence fence_out{}; // returned new fence object for others to wait on
-
- u32 AddIncrementValue() const {
- return flags.add_increment.Value() << 1;
- }
+ NvFence fence{}; // returned new fence object for others to wait on
};
- static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(Fence),
+ static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(NvFence),
"IoctlSubmitGpfifo is incorrect size");
struct IoctlGetWaitbase {
@@ -191,9 +204,18 @@ private:
NvResult ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
NvResult ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output);
- std::shared_ptr<nvmap> nvmap_dev;
- SyncpointManager& syncpoint_manager;
- Fence channel_fence;
+ EventInterface& events_interface;
+ NvCore::Container& core;
+ NvCore::SyncpointManager& syncpoint_manager;
+ NvCore::NvMap& nvmap;
+ std::shared_ptr<Tegra::Control::ChannelState> channel_state;
+ u32 channel_syncpoint;
+ std::mutex channel_mutex;
+
+ // Events
+ Kernel::KEvent* sm_exception_breakpoint_int_report_event;
+ Kernel::KEvent* sm_exception_breakpoint_pause_report_event;
+ Kernel::KEvent* error_notifier_event;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index 8314d1ec2..1703f9cc3 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -1,18 +1,18 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include "audio_core/audio_core.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
+#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
#include "video_core/renderer_base.h"
namespace Service::Nvidia::Devices {
-nvhost_nvdec::nvhost_nvdec(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_)
- : nvhost_nvdec_common{system_, std::move(nvmap_dev_), syncpoint_manager_} {}
+nvhost_nvdec::nvhost_nvdec(Core::System& system_, NvCore::Container& core_)
+ : nvhost_nvdec_common{system_, core_, NvCore::ChannelType::NvDec} {}
nvhost_nvdec::~nvhost_nvdec() = default;
NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -21,8 +21,9 @@ NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>&
case 0x0:
switch (command.cmd) {
case 0x1: {
- if (!fd_to_id.contains(fd)) {
- fd_to_id[fd] = next_id++;
+ auto& host1x_file = core.Host1xDeviceFile();
+ if (!host1x_file.fd_to_id.contains(fd)) {
+ host1x_file.fd_to_id[fd] = host1x_file.nvdec_next_id++;
}
return Submit(fd, input, output);
}
@@ -66,14 +67,19 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>&
return NvResult::NotImplemented;
}
-void nvhost_nvdec::OnOpen(DeviceFD fd) {}
+void nvhost_nvdec::OnOpen(DeviceFD fd) {
+ LOG_INFO(Service_NVDRV, "NVDEC video stream started");
+ system.AudioCore().SetNVDECActive(true);
+}
void nvhost_nvdec::OnClose(DeviceFD fd) {
LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
- const auto iter = fd_to_id.find(fd);
- if (iter != fd_to_id.end()) {
+ auto& host1x_file = core.Host1xDeviceFile();
+ const auto iter = host1x_file.fd_to_id.find(fd);
+ if (iter != host1x_file.fd_to_id.end()) {
system.GPU().ClearCdmaInstance(iter->second);
}
+ system.AudioCore().SetNVDECActive(false);
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
index a507c4d0a..c1b4e53e8 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -11,8 +10,7 @@ namespace Service::Nvidia::Devices {
class nvhost_nvdec final : public nvhost_nvdec_common {
public:
- explicit nvhost_nvdec(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_);
+ explicit nvhost_nvdec(Core::System& system_, NvCore::Container& core);
~nvhost_nvdec() override;
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -24,9 +22,6 @@ public:
void OnOpen(DeviceFD fd) override;
void OnClose(DeviceFD fd) override;
-
-private:
- u32 next_id{};
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
index 8a05f0668..99eede702 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
@@ -9,10 +8,12 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
-#include "core/hle/service/nvdrv/devices/nvmap.h"
-#include "core/hle/service/nvdrv/syncpoint_manager.h"
#include "core/memory.h"
+#include "video_core/host1x/host1x.h"
#include "video_core/memory_manager.h"
#include "video_core/renderer_base.h"
@@ -45,10 +46,22 @@ std::size_t WriteVectors(std::vector<u8>& dst, const std::vector<T>& src, std::s
}
} // Anonymous namespace
-nvhost_nvdec_common::nvhost_nvdec_common(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_)
- : nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)}, syncpoint_manager{syncpoint_manager_} {}
-nvhost_nvdec_common::~nvhost_nvdec_common() = default;
+nvhost_nvdec_common::nvhost_nvdec_common(Core::System& system_, NvCore::Container& core_,
+ NvCore::ChannelType channel_type_)
+ : nvdevice{system_}, core{core_}, syncpoint_manager{core.GetSyncpointManager()},
+ nvmap{core.GetNvMapFile()}, channel_type{channel_type_} {
+ auto& syncpts_accumulated = core.Host1xDeviceFile().syncpts_accumulated;
+ if (syncpts_accumulated.empty()) {
+ channel_syncpoint = syncpoint_manager.AllocateSyncpoint(false);
+ } else {
+ channel_syncpoint = syncpts_accumulated.front();
+ syncpts_accumulated.pop_front();
+ }
+}
+
+nvhost_nvdec_common::~nvhost_nvdec_common() {
+ core.Host1xDeviceFile().syncpts_accumulated.push_back(channel_syncpoint);
+}
NvResult nvhost_nvdec_common::SetNVMAPfd(const std::vector<u8>& input) {
IoctlSetNvmapFD params{};
@@ -85,16 +98,16 @@ NvResult nvhost_nvdec_common::Submit(DeviceFD fd, const std::vector<u8>& input,
for (std::size_t i = 0; i < syncpt_increments.size(); i++) {
const SyncptIncr& syncpt_incr = syncpt_increments[i];
fence_thresholds[i] =
- syncpoint_manager.IncreaseSyncpoint(syncpt_incr.id, syncpt_incr.increments);
+ syncpoint_manager.IncrementSyncpointMaxExt(syncpt_incr.id, syncpt_incr.increments);
}
}
for (const auto& cmd_buffer : command_buffers) {
- const auto object = nvmap_dev->GetObject(cmd_buffer.memory_id);
+ const auto object = nvmap.GetHandle(cmd_buffer.memory_id);
ASSERT_OR_EXECUTE(object, return NvResult::InvalidState;);
Tegra::ChCommandHeaderList cmdlist(cmd_buffer.word_count);
- system.Memory().ReadBlock(object->addr + cmd_buffer.offset, cmdlist.data(),
+ system.Memory().ReadBlock(object->address + cmd_buffer.offset, cmdlist.data(),
cmdlist.size() * sizeof(u32));
- gpu.PushCommandBuffer(fd_to_id[fd], cmdlist);
+ gpu.PushCommandBuffer(core.Host1xDeviceFile().fd_to_id[fd], cmdlist);
}
std::memcpy(output.data(), &params, sizeof(IoctlSubmit));
// Some games expect command_buffers to be written back
@@ -113,10 +126,8 @@ NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::ve
std::memcpy(&params, input.data(), sizeof(IoctlGetSyncpoint));
LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param);
- if (device_syncpoints[params.param] == 0 && system.GPU().UseNvdec()) {
- device_syncpoints[params.param] = syncpoint_manager.AllocateSyncpoint();
- }
- params.value = device_syncpoints[params.param];
+ // const u32 id{NvCore::SyncpointManager::channel_syncpoints[static_cast<u32>(channel_type)]};
+ params.value = channel_syncpoint;
std::memcpy(output.data(), &params, sizeof(IoctlGetSyncpoint));
return NvResult::Success;
@@ -124,6 +135,7 @@ NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::ve
NvResult nvhost_nvdec_common::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlGetWaitbase params{};
+ LOG_CRITICAL(Service_NVDRV, "called WAITBASE");
std::memcpy(&params, input.data(), sizeof(IoctlGetWaitbase));
params.value = 0; // Seems to be hard coded at 0
std::memcpy(output.data(), &params, sizeof(IoctlGetWaitbase));
@@ -137,28 +149,8 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto
SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
- auto& gpu = system.GPU();
-
for (auto& cmd_buffer : cmd_buffer_handles) {
- auto object{nvmap_dev->GetObject(cmd_buffer.map_handle)};
- if (!object) {
- LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmd_buffer.map_handle);
- std::memcpy(output.data(), &params, output.size());
- return NvResult::InvalidState;
- }
- if (object->dma_map_addr == 0) {
- // NVDEC and VIC memory is in the 32-bit address space
- // MapAllocate32 will attempt to map a lower 32-bit value in the shared gpu memory space
- const GPUVAddr low_addr = gpu.MemoryManager().MapAllocate32(object->addr, object->size);
- object->dma_map_addr = static_cast<u32>(low_addr);
- // Ensure that the dma_map_addr is indeed in the lower 32-bit address space.
- ASSERT(object->dma_map_addr == low_addr);
- }
- if (!object->dma_map_addr) {
- LOG_ERROR(Service_NVDRV, "failed to map size={}", object->size);
- } else {
- cmd_buffer.map_address = object->dma_map_addr;
- }
+ cmd_buffer.map_address = nvmap.PinHandle(cmd_buffer.map_handle);
}
std::memcpy(output.data(), &params, sizeof(IoctlMapBuffer));
std::memcpy(output.data() + sizeof(IoctlMapBuffer), cmd_buffer_handles.data(),
@@ -168,11 +160,16 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto
}
NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
- // This is intntionally stubbed.
- // Skip unmapping buffers here, as to not break the continuity of the VP9 reference frame
- // addresses, and risk invalidating data before the async GPU thread is done with it
+ IoctlMapBuffer params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlMapBuffer));
+ std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries);
+
+ SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
+ for (auto& cmd_buffer : cmd_buffer_handles) {
+ nvmap.UnpinHandle(cmd_buffer.map_handle);
+ }
+
std::memset(output.data(), 0, output.size());
- LOG_DEBUG(Service_NVDRV, "(STUBBED) called");
return NvResult::Success;
}
@@ -183,4 +180,9 @@ NvResult nvhost_nvdec_common::SetSubmitTimeout(const std::vector<u8>& input,
return NvResult::Success;
}
+Kernel::KEvent* nvhost_nvdec_common::QueryEvent(u32 event_id) {
+ LOG_CRITICAL(Service_NVDRV, "Unknown HOSTX1 Event {}", event_id);
+ return nullptr;
+}
+
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
index e28c54df6..fe76100c8 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
@@ -1,24 +1,28 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include <deque>
#include <vector>
#include "common/common_types.h"
#include "common/swap.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
namespace Service::Nvidia {
-class SyncpointManager;
+
+namespace NvCore {
+class Container;
+class NvMap;
+} // namespace NvCore
namespace Devices {
-class nvmap;
class nvhost_nvdec_common : public nvdevice {
public:
- explicit nvhost_nvdec_common(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_);
+ explicit nvhost_nvdec_common(Core::System& system_, NvCore::Container& core,
+ NvCore::ChannelType channel_type);
~nvhost_nvdec_common() override;
protected:
@@ -111,11 +115,15 @@ protected:
NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output);
- std::unordered_map<DeviceFD, u32> fd_to_id{};
+ Kernel::KEvent* QueryEvent(u32 event_id) override;
+
+ u32 channel_syncpoint;
s32_le nvmap_fd{};
u32_le submit_timeout{};
- std::shared_ptr<nvmap> nvmap_dev;
- SyncpointManager& syncpoint_manager;
+ NvCore::Container& core;
+ NvCore::SyncpointManager& syncpoint_manager;
+ NvCore::NvMap& nvmap;
+ NvCore::ChannelType channel_type;
std::array<u32, MaxSyncPoints> device_syncpoints{};
};
}; // namespace Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
index c2be3cea7..bdbc2f9e1 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
index 6045e5cbd..440e7d371 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
index 76b39806f..73f97136e 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
@@ -1,17 +1,17 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
+#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/devices/nvhost_vic.h"
#include "video_core/renderer_base.h"
namespace Service::Nvidia::Devices {
-nvhost_vic::nvhost_vic(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_)
- : nvhost_nvdec_common{system_, std::move(nvmap_dev_), syncpoint_manager_} {}
+
+nvhost_vic::nvhost_vic(Core::System& system_, NvCore::Container& core_)
+ : nvhost_nvdec_common{system_, core_, NvCore::ChannelType::VIC} {}
nvhost_vic::~nvhost_vic() = default;
@@ -20,11 +20,13 @@ NvResult nvhost_vic::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& i
switch (command.group) {
case 0x0:
switch (command.cmd) {
- case 0x1:
- if (!fd_to_id.contains(fd)) {
- fd_to_id[fd] = next_id++;
+ case 0x1: {
+ auto& host1x_file = core.Host1xDeviceFile();
+ if (!host1x_file.fd_to_id.contains(fd)) {
+ host1x_file.fd_to_id[fd] = host1x_file.vic_next_id++;
}
return Submit(fd, input, output);
+ }
case 0x2:
return GetSyncpoint(input, output);
case 0x3:
@@ -68,8 +70,9 @@ NvResult nvhost_vic::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& i
void nvhost_vic::OnOpen(DeviceFD fd) {}
void nvhost_vic::OnClose(DeviceFD fd) {
- const auto iter = fd_to_id.find(fd);
- if (iter != fd_to_id.end()) {
+ auto& host1x_file = core.Host1xDeviceFile();
+ const auto iter = host1x_file.fd_to_id.find(fd);
+ if (iter != host1x_file.fd_to_id.end()) {
system.GPU().ClearCdmaInstance(iter->second);
}
}
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h
index c9732c037..f164caafb 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,8 +9,7 @@ namespace Service::Nvidia::Devices {
class nvhost_vic final : public nvhost_nvdec_common {
public:
- explicit nvhost_vic(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_);
+ explicit nvhost_vic(Core::System& system_, NvCore::Container& core);
~nvhost_vic();
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -23,8 +21,5 @@ public:
void OnOpen(DeviceFD fd) override;
void OnClose(DeviceFD fd) override;
-
-private:
- u32 next_id{};
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index dc59b4494..ddf273b5e 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -1,21 +1,27 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
+#include <bit>
#include <cstring>
+#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hle/kernel/k_page_table.h"
+#include "core/hle/kernel/k_process.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvdrv/devices/nvmap.h"
+#include "core/memory.h"
+
+using Core::Memory::YUZU_PAGESIZE;
namespace Service::Nvidia::Devices {
-nvmap::nvmap(Core::System& system_) : nvdevice{system_} {
- // Handle 0 appears to be used when remapping, so we create a placeholder empty nvmap object to
- // represent this.
- CreateObject(0);
-}
+nvmap::nvmap(Core::System& system_, NvCore::Container& container_)
+ : nvdevice{system_}, container{container_}, file{container.GetNvMapFile()} {}
nvmap::~nvmap() = default;
@@ -63,39 +69,21 @@ NvResult nvmap::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
void nvmap::OnOpen(DeviceFD fd) {}
void nvmap::OnClose(DeviceFD fd) {}
-VAddr nvmap::GetObjectAddress(u32 handle) const {
- auto object = GetObject(handle);
- ASSERT(object);
- ASSERT(object->status == Object::Status::Allocated);
- return object->addr;
-}
-
-u32 nvmap::CreateObject(u32 size) {
- // Create a new nvmap object and obtain a handle to it.
- auto object = std::make_shared<Object>();
- object->id = next_id++;
- object->size = size;
- object->status = Object::Status::Created;
- object->refcount = 1;
-
- const u32 handle = next_handle++;
-
- handles.insert_or_assign(handle, std::move(object));
-
- return handle;
-}
-
NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
IocCreateParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size);
-
- if (!params.size) {
- LOG_ERROR(Service_NVDRV, "Size is 0");
- return NvResult::BadValue;
+ LOG_DEBUG(Service_NVDRV, "called, size=0x{:08X}", params.size);
+
+ std::shared_ptr<NvCore::NvMap::Handle> handle_description{};
+ auto result =
+ file.CreateHandle(Common::AlignUp(params.size, YUZU_PAGESIZE), handle_description);
+ if (result != NvResult::Success) {
+ LOG_CRITICAL(Service_NVDRV, "Failed to create Object");
+ return result;
}
-
- params.handle = CreateObject(params.size);
+ handle_description->orig_size = params.size; // Orig size is the unaligned size
+ params.handle = handle_description->id;
+ LOG_DEBUG(Service_NVDRV, "handle: {}, size: 0x{:X}", handle_description->id, params.size);
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
@@ -104,63 +92,68 @@ NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output)
NvResult nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
IocAllocParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr);
+ LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.address);
if (!params.handle) {
- LOG_ERROR(Service_NVDRV, "Handle is 0");
+ LOG_CRITICAL(Service_NVDRV, "Handle is 0");
return NvResult::BadValue;
}
if ((params.align - 1) & params.align) {
- LOG_ERROR(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align);
+ LOG_CRITICAL(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align);
return NvResult::BadValue;
}
- const u32 min_alignment = 0x1000;
- if (params.align < min_alignment) {
- params.align = min_alignment;
+ // Force page size alignment at a minimum
+ if (params.align < YUZU_PAGESIZE) {
+ params.align = YUZU_PAGESIZE;
}
- auto object = GetObject(params.handle);
- if (!object) {
- LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
+ auto handle_description{file.GetHandle(params.handle)};
+ if (!handle_description) {
+ LOG_CRITICAL(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
return NvResult::BadValue;
}
- if (object->status == Object::Status::Allocated) {
- LOG_ERROR(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
+ if (handle_description->allocated) {
+ LOG_CRITICAL(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
return NvResult::InsufficientMemory;
}
- object->flags = params.flags;
- object->align = params.align;
- object->kind = params.kind;
- object->addr = params.addr;
- object->status = Object::Status::Allocated;
-
+ const auto result =
+ handle_description->Alloc(params.flags, params.align, params.kind, params.address);
+ if (result != NvResult::Success) {
+ LOG_CRITICAL(Service_NVDRV, "Object failed to allocate, handle={:08X}", params.handle);
+ return result;
+ }
+ ASSERT(system.CurrentProcess()
+ ->PageTable()
+ .LockForDeviceAddressSpace(handle_description->address, handle_description->size)
+ .IsSuccess());
std::memcpy(output.data(), &params, sizeof(params));
- return NvResult::Success;
+ return result;
}
NvResult nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
IocGetIdParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service_NVDRV, "called");
+ LOG_DEBUG(Service_NVDRV, "called");
+ // See the comment in FromId for extra info on this function
if (!params.handle) {
- LOG_ERROR(Service_NVDRV, "Handle is zero");
+ LOG_CRITICAL(Service_NVDRV, "Error!");
return NvResult::BadValue;
}
- auto object = GetObject(params.handle);
- if (!object) {
- LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- return NvResult::BadValue;
+ auto handle_description{file.GetHandle(params.handle)};
+ if (!handle_description) {
+ LOG_CRITICAL(Service_NVDRV, "Error!");
+ return NvResult::AccessDenied; // This will always return EPERM irrespective of if the
+ // handle exists or not
}
- params.id = object->id;
-
+ params.id = handle_description->id;
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
}
@@ -169,26 +162,29 @@ NvResult nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output)
IocFromIdParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ LOG_DEBUG(Service_NVDRV, "called, id:{}", params.id);
- auto itr = std::find_if(handles.begin(), handles.end(),
- [&](const auto& entry) { return entry.second->id == params.id; });
- if (itr == handles.end()) {
- LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
+ // Handles and IDs are always the same value in nvmap however IDs can be used globally given the
+ // right permissions.
+ // Since we don't plan on ever supporting multiprocess we can skip implementing handle refs and
+ // so this function just does simple validation and passes through the handle id.
+ if (!params.id) {
+ LOG_CRITICAL(Service_NVDRV, "Zero Id is invalid!");
return NvResult::BadValue;
}
- auto& object = itr->second;
- if (object->status != Object::Status::Allocated) {
- LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle);
+ auto handle_description{file.GetHandle(params.id)};
+ if (!handle_description) {
+ LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
return NvResult::BadValue;
}
- itr->second->refcount++;
-
- // Return the existing handle instead of creating a new one.
- params.handle = itr->first;
-
+ auto result = handle_description->Duplicate(false);
+ if (result != NvResult::Success) {
+ LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
+ return result;
+ }
+ params.handle = handle_description->id;
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
}
@@ -199,35 +195,43 @@ NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output)
IocParamParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service_NVDRV, "(STUBBED) called type={}", params.param);
+ LOG_DEBUG(Service_NVDRV, "called type={}", params.param);
- auto object = GetObject(params.handle);
- if (!object) {
- LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
+ if (!params.handle) {
+ LOG_CRITICAL(Service_NVDRV, "Invalid handle!");
return NvResult::BadValue;
}
- if (object->status != Object::Status::Allocated) {
- LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle);
+ auto handle_description{file.GetHandle(params.handle)};
+ if (!handle_description) {
+ LOG_CRITICAL(Service_NVDRV, "Not registered handle!");
return NvResult::BadValue;
}
- switch (static_cast<ParamTypes>(params.param)) {
- case ParamTypes::Size:
- params.result = object->size;
+ switch (params.param) {
+ case HandleParameterType::Size:
+ params.result = static_cast<u32_le>(handle_description->orig_size);
+ break;
+ case HandleParameterType::Alignment:
+ params.result = static_cast<u32_le>(handle_description->align);
break;
- case ParamTypes::Alignment:
- params.result = object->align;
+ case HandleParameterType::Base:
+ params.result = static_cast<u32_le>(-22); // posix EINVAL
break;
- case ParamTypes::Heap:
- // TODO(Subv): Seems to be a hardcoded value?
- params.result = 0x40000000;
+ case HandleParameterType::Heap:
+ if (handle_description->allocated)
+ params.result = 0x40000000;
+ else
+ params.result = 0;
break;
- case ParamTypes::Kind:
- params.result = object->kind;
+ case HandleParameterType::Kind:
+ params.result = handle_description->kind;
+ break;
+ case HandleParameterType::IsSharedMemMapped:
+ params.result = handle_description->is_shared_mem_mapped;
break;
default:
- UNIMPLEMENTED();
+ return NvResult::BadValue;
}
std::memcpy(output.data(), &params, sizeof(params));
@@ -235,46 +239,29 @@ NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output)
}
NvResult nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
- // TODO(Subv): These flags are unconfirmed.
- enum FreeFlags {
- Freed = 0,
- NotFreedYet = 1,
- };
-
IocFreeParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ LOG_DEBUG(Service_NVDRV, "called");
- auto itr = handles.find(params.handle);
- if (itr == handles.end()) {
- LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- return NvResult::BadValue;
- }
- if (!itr->second->refcount) {
- LOG_ERROR(
- Service_NVDRV,
- "There is no references to this object. The object is already freed. handle={:08X}",
- params.handle);
- return NvResult::BadValue;
+ if (!params.handle) {
+ LOG_CRITICAL(Service_NVDRV, "Handle null freed?");
+ return NvResult::Success;
}
- itr->second->refcount--;
-
- params.size = itr->second->size;
-
- if (itr->second->refcount == 0) {
- params.flags = Freed;
- // The address of the nvmap is written to the output if we're finally freeing it, otherwise
- // 0 is written.
- params.address = itr->second->addr;
+ if (auto freeInfo{file.FreeHandle(params.handle, false)}) {
+ ASSERT(system.CurrentProcess()
+ ->PageTable()
+ .UnlockForDeviceAddressSpace(freeInfo->address, freeInfo->size)
+ .IsSuccess());
+ params.address = freeInfo->address;
+ params.size = static_cast<u32>(freeInfo->size);
+ params.flags.raw = 0;
+ params.flags.map_uncached.Assign(freeInfo->was_uncached);
} else {
- params.flags = NotFreedYet;
- params.address = 0;
+ // This is possible when there's internel dups or other duplicates.
}
- handles.erase(params.handle);
-
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index d90b69e5a..e9bfd0358 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,15 +9,23 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
+namespace Service::Nvidia::NvCore {
+class Container;
+} // namespace Service::Nvidia::NvCore
+
namespace Service::Nvidia::Devices {
class nvmap final : public nvdevice {
public:
- explicit nvmap(Core::System& system_);
+ explicit nvmap(Core::System& system_, NvCore::Container& container);
~nvmap() override;
+ nvmap(const nvmap&) = delete;
+ nvmap& operator=(const nvmap&) = delete;
+
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
std::vector<u8>& output) override;
NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -29,31 +36,15 @@ public:
void OnOpen(DeviceFD fd) override;
void OnClose(DeviceFD fd) override;
- /// Returns the allocated address of an nvmap object given its handle.
- VAddr GetObjectAddress(u32 handle) const;
-
- /// Represents an nvmap object.
- struct Object {
- enum class Status { Created, Allocated };
- u32 id;
- u32 size;
- u32 flags;
- u32 align;
- u8 kind;
- VAddr addr;
- Status status;
- u32 refcount;
- u32 dma_map_addr;
+ enum class HandleParameterType : u32_le {
+ Size = 1,
+ Alignment = 2,
+ Base = 3,
+ Heap = 4,
+ Kind = 5,
+ IsSharedMemMapped = 6
};
- std::shared_ptr<Object> GetObject(u32 handle) const {
- auto itr = handles.find(handle);
- if (itr != handles.end()) {
- return itr->second;
- }
- return {};
- }
-
private:
/// Id to use for the next handle that is created.
u32 next_handle = 0;
@@ -61,9 +52,6 @@ private:
/// Id to use for the next object that is created.
u32 next_id = 0;
- /// Mapping of currently allocated handles to the objects they represent.
- std::unordered_map<u32, std::shared_ptr<Object>> handles;
-
struct IocCreateParams {
// Input
u32_le size{};
@@ -84,11 +72,11 @@ private:
// Input
u32_le handle{};
u32_le heap_mask{};
- u32_le flags{};
+ NvCore::NvMap::Handle::Flags flags{};
u32_le align{};
u8 kind{};
INSERT_PADDING_BYTES(7);
- u64_le addr{};
+ u64_le address{};
};
static_assert(sizeof(IocAllocParams) == 32, "IocAllocParams has wrong size");
@@ -97,14 +85,14 @@ private:
INSERT_PADDING_BYTES(4);
u64_le address{};
u32_le size{};
- u32_le flags{};
+ NvCore::NvMap::Handle::Flags flags{};
};
static_assert(sizeof(IocFreeParams) == 24, "IocFreeParams has wrong size");
struct IocParamParams {
// Input
u32_le handle{};
- u32_le param{};
+ HandleParameterType param{};
// Output
u32_le result{};
};
@@ -118,14 +106,15 @@ private:
};
static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");
- u32 CreateObject(u32 size);
-
NvResult IocCreate(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocAlloc(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocGetId(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocFromId(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocParam(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocFree(const std::vector<u8>& input, std::vector<u8>& output);
+
+ NvCore::Container& container;
+ NvCore::NvMap& file;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/nvdata.h b/src/core/hle/service/nvdrv/nvdata.h
index 5ab221fc1..0e2f47075 100644
--- a/src/core/hle/service/nvdrv/nvdata.h
+++ b/src/core/hle/service/nvdrv/nvdata.h
@@ -1,6 +1,6 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -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,
@@ -85,11 +79,15 @@ enum class NvResult : u32 {
ModuleNotPresent = 0xA000E,
};
+// obtained from
+// https://github.com/skyline-emu/skyline/blob/nvdec-dev/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.h#L47
enum class EventState {
- Free = 0,
- Registered = 1,
- Waiting = 2,
- Busy = 3,
+ Available = 0,
+ Waiting = 1,
+ Cancelling = 2,
+ Signalling = 3,
+ Signalled = 4,
+ Cancelled = 5,
};
union Ioctl {
diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp
index aa7e47cbf..5e7b7468f 100644
--- a/src/core/hle/service/nvdrv/nvdrv.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv.cpp
@@ -1,6 +1,6 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
#include <utility>
@@ -9,6 +9,7 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_writable_event.h"
+#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
@@ -16,17 +17,31 @@
#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
+#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvjpg.h"
#include "core/hle/service/nvdrv/devices/nvhost_vic.h"
#include "core/hle/service/nvdrv/devices/nvmap.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvdrv/nvdrv_interface.h"
#include "core/hle/service/nvdrv/nvmemp.h"
-#include "core/hle/service/nvdrv/syncpoint_manager.h"
#include "core/hle/service/nvflinger/nvflinger.h"
+#include "video_core/gpu.h"
namespace Service::Nvidia {
+EventInterface::EventInterface(Module& module_) : module{module_}, guard{}, on_signal{} {}
+
+EventInterface::~EventInterface() = default;
+
+Kernel::KEvent* EventInterface::CreateEvent(std::string name) {
+ Kernel::KEvent* new_event = module.service_context.CreateEvent(std::move(name));
+ return new_event;
+}
+
+void EventInterface::FreeEvent(Kernel::KEvent* event) {
+ module.service_context.CloseEvent(event);
+}
+
void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
Core::System& system) {
auto module_ = std::make_shared<Module>(system);
@@ -39,34 +54,54 @@ void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger
}
Module::Module(Core::System& system)
- : syncpoint_manager{system.GPU()}, service_context{system, "nvdrv"} {
- for (u32 i = 0; i < MaxNvEvents; i++) {
- events_interface.events[i].event =
- service_context.CreateEvent(fmt::format("NVDRV::NvEvent_{}", i));
- events_interface.status[i] = EventState::Free;
- events_interface.registered[i] = false;
- }
- auto nvmap_dev = std::make_shared<Devices::nvmap>(system);
- devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(system, nvmap_dev);
- devices["/dev/nvhost-gpu"] =
- std::make_shared<Devices::nvhost_gpu>(system, nvmap_dev, syncpoint_manager);
- devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>(system);
- devices["/dev/nvmap"] = nvmap_dev;
- devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(system, nvmap_dev);
- devices["/dev/nvhost-ctrl"] =
- std::make_shared<Devices::nvhost_ctrl>(system, events_interface, syncpoint_manager);
- devices["/dev/nvhost-nvdec"] =
- std::make_shared<Devices::nvhost_nvdec>(system, nvmap_dev, syncpoint_manager);
- devices["/dev/nvhost-nvjpg"] = std::make_shared<Devices::nvhost_nvjpg>(system);
- devices["/dev/nvhost-vic"] =
- std::make_shared<Devices::nvhost_vic>(system, nvmap_dev, syncpoint_manager);
+ : service_context{system, "nvdrv"}, events_interface{*this}, container{system.Host1x()} {
+ builders["/dev/nvhost-as-gpu"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvhost_as_gpu>(system, *this, container);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvhost-gpu"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvhost_gpu>(system, events_interface, container);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvhost-ctrl-gpu"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvhost_ctrl_gpu>(system, events_interface);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvmap"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvmap>(system, container);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvdisp_disp0"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvdisp_disp0>(system, container);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvhost-ctrl"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvhost_ctrl>(system, events_interface, container);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvhost-nvdec"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvhost_nvdec>(system, container);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvhost-nvjpg"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device = std::make_shared<Devices::nvhost_nvjpg>(system);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvhost-vic"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvhost_vic>(system, container);
+ return open_files.emplace(fd, device).first;
+ };
}
-Module::~Module() {
- for (u32 i = 0; i < MaxNvEvents; i++) {
- service_context.CloseEvent(events_interface.events[i].event);
- }
-}
+Module::~Module() {}
NvResult Module::VerifyFD(DeviceFD fd) const {
if (fd < 0) {
@@ -83,18 +118,18 @@ NvResult Module::VerifyFD(DeviceFD fd) const {
}
DeviceFD Module::Open(const std::string& device_name) {
- if (devices.find(device_name) == devices.end()) {
+ auto it = builders.find(device_name);
+ if (it == builders.end()) {
LOG_ERROR(Service_NVDRV, "Trying to open unknown device {}", device_name);
return INVALID_NVDRV_FD;
}
- auto device = devices[device_name];
const DeviceFD fd = next_fd++;
+ auto& builder = it->second;
+ auto device = builder(fd)->second;
device->OnOpen(fd);
- open_files[fd] = std::move(device);
-
return fd;
}
@@ -169,22 +204,24 @@ NvResult Module::Close(DeviceFD fd) {
return NvResult::Success;
}
-void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) {
- for (u32 i = 0; i < MaxNvEvents; i++) {
- if (events_interface.assigned_syncpt[i] == syncpoint_id &&
- events_interface.assigned_value[i] == value) {
- events_interface.LiberateEvent(i);
- events_interface.events[i].event->GetWritableEvent().Signal();
- }
+NvResult Module::QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event) {
+ if (fd < 0) {
+ LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
+ return NvResult::InvalidState;
}
-}
-Kernel::KReadableEvent& Module::GetEvent(const u32 event_id) {
- return events_interface.events[event_id].event->GetReadableEvent();
-}
+ const auto itr = open_files.find(fd);
-Kernel::KWritableEvent& Module::GetEventWriteable(const u32 event_id) {
- return events_interface.events[event_id].event->GetWritableEvent();
+ if (itr == open_files.end()) {
+ LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
+ return NvResult::NotImplemented;
+ }
+
+ event = itr->second->QueryEvent(event_id);
+ if (!event) {
+ return NvResult::BadParameter;
+ }
+ return NvResult::Success;
}
} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h
index a5af5b785..146d046a9 100644
--- a/src/core/hle/service/nvdrv/nvdrv.h
+++ b/src/core/hle/service/nvdrv/nvdrv.h
@@ -1,17 +1,21 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
+#include <functional>
+#include <list>
#include <memory>
+#include <string>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nvdrv/core/container.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 {
@@ -28,81 +32,31 @@ class NVFlinger;
namespace Service::Nvidia {
+namespace NvCore {
+class Container;
class SyncpointManager;
+} // namespace NvCore
namespace Devices {
class nvdevice;
-}
+class nvhost_ctrl;
+} // namespace Devices
-/// Represents an Nvidia event
-struct NvEvent {
- Kernel::KEvent* event{};
- Fence fence{};
-};
+class Module;
-struct EventInterface {
- // Mask representing currently busy events
- u64 events_mask{};
- // Each kernel event associated to an NV event
- std::array<NvEvent, MaxNvEvents> events;
- // The status of the current NVEvent
- std::array<EventState, MaxNvEvents> status{};
- // Tells if an NVEvent is registered or not
- std::array<bool, MaxNvEvents> registered{};
- // Tells the NVEvent that it has failed.
- std::array<bool, MaxNvEvents> failed{};
- // When an NVEvent is waiting on GPU interrupt, this is the sync_point
- // associated with it.
- std::array<u32, MaxNvEvents> assigned_syncpt{};
- // This is the value of the GPU interrupt for which the NVEvent is waiting
- // for.
- std::array<u32, MaxNvEvents> assigned_value{};
- // Constant to denote an unasigned syncpoint.
- static constexpr u32 unassigned_syncpt = 0xFFFFFFFF;
- std::optional<u32> GetFreeEvent() const {
- u64 mask = events_mask;
- for (u32 i = 0; i < MaxNvEvents; i++) {
- const bool is_free = (mask & 0x1) == 0;
- if (is_free) {
- if (status[i] == EventState::Registered || status[i] == EventState::Free) {
- return {i};
- }
- }
- mask = mask >> 1;
- }
- return std::nullopt;
- }
- void SetEventStatus(const u32 event_id, EventState new_status) {
- EventState old_status = status[event_id];
- if (old_status == new_status) {
- return;
- }
- status[event_id] = new_status;
- if (new_status == EventState::Registered) {
- registered[event_id] = true;
- }
- if (new_status == EventState::Waiting || new_status == EventState::Busy) {
- events_mask |= (1ULL << event_id);
- }
- }
- void RegisterEvent(const u32 event_id) {
- registered[event_id] = true;
- if (status[event_id] == EventState::Free) {
- status[event_id] = EventState::Registered;
- }
- }
- void UnregisterEvent(const u32 event_id) {
- registered[event_id] = false;
- if (status[event_id] == EventState::Registered) {
- status[event_id] = EventState::Free;
- }
- }
- void LiberateEvent(const u32 event_id) {
- status[event_id] = registered[event_id] ? EventState::Registered : EventState::Free;
- events_mask &= ~(1ULL << event_id);
- assigned_syncpt[event_id] = unassigned_syncpt;
- assigned_value[event_id] = 0;
- }
+class EventInterface {
+public:
+ explicit EventInterface(Module& module_);
+ ~EventInterface();
+
+ Kernel::KEvent* CreateEvent(std::string name);
+
+ void FreeEvent(Kernel::KEvent* event);
+
+private:
+ Module& module;
+ std::mutex guard;
+ std::list<Devices::nvhost_ctrl*> on_signal;
};
class Module final {
@@ -112,9 +66,9 @@ public:
/// Returns a pointer to one of the available devices, identified by its name.
template <typename T>
- std::shared_ptr<T> GetDevice(const std::string& name) {
- auto itr = devices.find(name);
- if (itr == devices.end())
+ std::shared_ptr<T> GetDevice(DeviceFD fd) {
+ auto itr = open_files.find(fd);
+ if (itr == open_files.end())
return nullptr;
return std::static_pointer_cast<T>(itr->second);
}
@@ -137,28 +91,27 @@ public:
/// Closes a device file descriptor and returns operation success.
NvResult Close(DeviceFD fd);
- void SignalSyncpt(const u32 syncpoint_id, const u32 value);
-
- Kernel::KReadableEvent& GetEvent(u32 event_id);
-
- Kernel::KWritableEvent& GetEventWriteable(u32 event_id);
+ NvResult QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event);
private:
- /// Manages syncpoints on the host
- SyncpointManager syncpoint_manager;
+ friend class EventInterface;
+ friend class Service::NVFlinger::NVFlinger;
/// Id to use for the next open file descriptor.
DeviceFD next_fd = 1;
+ using FilesContainerType = std::unordered_map<DeviceFD, std::shared_ptr<Devices::nvdevice>>;
/// Mapping of file descriptors to the devices they reference.
- std::unordered_map<DeviceFD, std::shared_ptr<Devices::nvdevice>> open_files;
+ FilesContainerType open_files;
- /// Mapping of device node names to their implementation.
- std::unordered_map<std::string, std::shared_ptr<Devices::nvdevice>> devices;
+ KernelHelpers::ServiceContext service_context;
EventInterface events_interface;
- KernelHelpers::ServiceContext service_context;
+ /// Manages syncpoints on the host
+ NvCore::Container container;
+
+ std::unordered_map<std::string, std::function<FilesContainerType::iterator(DeviceFD)>> builders;
};
/// Registers all NVDRV services with the specified service manager.
diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.cpp b/src/core/hle/service/nvdrv/nvdrv_interface.cpp
index c16babe14..edbdfee43 100644
--- a/src/core/hle/service/nvdrv/nvdrv_interface.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv_interface.cpp
@@ -1,11 +1,12 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
#include <cinttypes>
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvdrv/nvdrv.h"
@@ -13,10 +14,6 @@
namespace Service::Nvidia {
-void NVDRV::SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
- nvdrv->SignalSyncpt(syncpoint_id, value);
-}
-
void NVDRV::Open(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NVDRV, "called");
IPC::ResponseBuilder rb{ctx, 4};
@@ -26,7 +23,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 +58,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 +84,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 +111,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 +136,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;
}
@@ -165,33 +162,28 @@ void NVDRV::Initialize(Kernel::HLERequestContext& ctx) {
void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto fd = rp.Pop<DeviceFD>();
- const auto event_id = rp.Pop<u32>() & 0x00FF;
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id);
+ const auto event_id = rp.Pop<u32>();
if (!is_initialized) {
ServiceError(ctx, NvResult::NotInitialized);
- LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ LOG_ERROR(Service_NVDRV, "NvServices is not initialized!");
return;
}
- const auto nv_result = nvdrv->VerifyFD(fd);
- if (nv_result != NvResult::Success) {
- LOG_ERROR(Service_NVDRV, "Invalid FD specified DeviceFD={}!", fd);
- ServiceError(ctx, nv_result);
- return;
- }
+ Kernel::KEvent* event = nullptr;
+ NvResult result = nvdrv->QueryEvent(fd, event_id, event);
- if (event_id < MaxNvEvents) {
+ if (result == NvResult::Success) {
IPC::ResponseBuilder rb{ctx, 3, 1};
rb.Push(ResultSuccess);
- auto& event = nvdrv->GetEvent(event_id);
- event.Clear();
- rb.PushCopyObjects(event);
+ auto& readable_event = event->GetReadableEvent();
+ rb.PushCopyObjects(readable_event);
rb.PushEnum(NvResult::Success);
} else {
+ LOG_ERROR(Service_NVDRV, "Invalid event request!");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.PushEnum(NvResult::BadParameter);
+ rb.PushEnum(result);
}
}
@@ -230,7 +222,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/nvdrv/nvdrv_interface.h b/src/core/hle/service/nvdrv/nvdrv_interface.h
index 0e764c53f..cd58a4f35 100644
--- a/src/core/hle/service/nvdrv/nvdrv_interface.h
+++ b/src/core/hle/service/nvdrv/nvdrv_interface.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -19,8 +18,6 @@ public:
explicit NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name);
~NVDRV() override;
- void SignalGPUInterruptSyncpt(u32 syncpoint_id, u32 value);
-
private:
void Open(Kernel::HLERequestContext& ctx);
void Ioctl1(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/nvdrv/nvmemp.cpp b/src/core/hle/service/nvdrv/nvmemp.cpp
index 331c02243..e433580b1 100644
--- a/src/core/hle/service/nvdrv/nvmemp.cpp
+++ b/src/core/hle/service/nvdrv/nvmemp.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/logging/log.h"
diff --git a/src/core/hle/service/nvdrv/nvmemp.h b/src/core/hle/service/nvdrv/nvmemp.h
index 724c27ef9..3d4276327 100644
--- a/src/core/hle/service/nvdrv/nvmemp.h
+++ b/src/core/hle/service/nvdrv/nvmemp.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/nvdrv/syncpoint_manager.cpp b/src/core/hle/service/nvdrv/syncpoint_manager.cpp
deleted file mode 100644
index 3b6f55526..000000000
--- a/src/core/hle/service/nvdrv/syncpoint_manager.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/assert.h"
-#include "core/hle/service/nvdrv/syncpoint_manager.h"
-#include "video_core/gpu.h"
-
-namespace Service::Nvidia {
-
-SyncpointManager::SyncpointManager(Tegra::GPU& gpu_) : gpu{gpu_} {}
-
-SyncpointManager::~SyncpointManager() = default;
-
-u32 SyncpointManager::RefreshSyncpoint(u32 syncpoint_id) {
- syncpoints[syncpoint_id].min = gpu.GetSyncpointValue(syncpoint_id);
- return GetSyncpointMin(syncpoint_id);
-}
-
-u32 SyncpointManager::AllocateSyncpoint() {
- for (u32 syncpoint_id = 1; syncpoint_id < MaxSyncPoints; syncpoint_id++) {
- if (!syncpoints[syncpoint_id].is_allocated) {
- syncpoints[syncpoint_id].is_allocated = true;
- return syncpoint_id;
- }
- }
- UNREACHABLE_MSG("No more available syncpoints!");
- return {};
-}
-
-u32 SyncpointManager::IncreaseSyncpoint(u32 syncpoint_id, u32 value) {
- for (u32 index = 0; index < value; ++index) {
- syncpoints[syncpoint_id].max.fetch_add(1, std::memory_order_relaxed);
- }
-
- return GetSyncpointMax(syncpoint_id);
-}
-
-} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/syncpoint_manager.h b/src/core/hle/service/nvdrv/syncpoint_manager.h
deleted file mode 100644
index 99f286474..000000000
--- a/src/core/hle/service/nvdrv/syncpoint_manager.h
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <atomic>
-
-#include "common/common_types.h"
-#include "core/hle/service/nvdrv/nvdata.h"
-
-namespace Tegra {
-class GPU;
-}
-
-namespace Service::Nvidia {
-
-class SyncpointManager final {
-public:
- explicit SyncpointManager(Tegra::GPU& gpu_);
- ~SyncpointManager();
-
- /**
- * Returns true if the specified syncpoint is expired for the given value.
- * @param syncpoint_id Syncpoint ID to check.
- * @param value Value to check against the specified syncpoint.
- * @returns True if the specified syncpoint is expired for the given value, otherwise False.
- */
- bool IsSyncpointExpired(u32 syncpoint_id, u32 value) const {
- return (GetSyncpointMax(syncpoint_id) - value) >= (GetSyncpointMin(syncpoint_id) - value);
- }
-
- /**
- * Gets the lower bound for the specified syncpoint.
- * @param syncpoint_id Syncpoint ID to get the lower bound for.
- * @returns The lower bound for the specified syncpoint.
- */
- u32 GetSyncpointMin(u32 syncpoint_id) const {
- return syncpoints.at(syncpoint_id).min.load(std::memory_order_relaxed);
- }
-
- /**
- * Gets the uper bound for the specified syncpoint.
- * @param syncpoint_id Syncpoint ID to get the upper bound for.
- * @returns The upper bound for the specified syncpoint.
- */
- u32 GetSyncpointMax(u32 syncpoint_id) const {
- return syncpoints.at(syncpoint_id).max.load(std::memory_order_relaxed);
- }
-
- /**
- * Refreshes the minimum value for the specified syncpoint.
- * @param syncpoint_id Syncpoint ID to be refreshed.
- * @returns The new syncpoint minimum value.
- */
- u32 RefreshSyncpoint(u32 syncpoint_id);
-
- /**
- * Allocates a new syncoint.
- * @returns The syncpoint ID for the newly allocated syncpoint.
- */
- u32 AllocateSyncpoint();
-
- /**
- * Increases the maximum value for the specified syncpoint.
- * @param syncpoint_id Syncpoint ID to be increased.
- * @param value Value to increase the specified syncpoint by.
- * @returns The new syncpoint maximum value.
- */
- u32 IncreaseSyncpoint(u32 syncpoint_id, u32 value);
-
-private:
- struct Syncpoint {
- std::atomic<u32> min;
- std::atomic<u32> max;
- std::atomic<bool> is_allocated;
- };
-
- std::array<Syncpoint, MaxSyncPoints> syncpoints{};
-
- Tegra::GPU& gpu;
-};
-
-} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvflinger/binder.h b/src/core/hle/service/nvflinger/binder.h
new file mode 100644
index 000000000..157333ff8
--- /dev/null
+++ b/src/core/hle/service/nvflinger/binder.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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 ~IBinder() = default;
+ 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..f73dec4f1
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_item.h
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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..6d2c92a2c
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_item_consumer.cpp
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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..69046233d
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_item_consumer.h
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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..1ce67c771
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp
@@ -0,0 +1,213 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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/nvdrv/core/nvmap.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"
+#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
+
+namespace Service::android {
+
+BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_,
+ Service::Nvidia::NvCore::NvMap& nvmap_)
+ : core{std::move(core_)}, slots{core->slots}, nvmap(nvmap_) {}
+
+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 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;
+ }
+
+ slots[slot].buffer_state = BufferState::Free;
+
+ nvmap.FreeHandle(slots[slot].graphic_buffer->BufferId(), true);
+
+ listener = core->connected_producer_listener;
+
+ LOG_DEBUG(Service_NVFlinger, "releasing slot {}", slot);
+
+ 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..4ec06ca13
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_consumer.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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::Nvidia::NvCore {
+class NvMap;
+} // namespace Service::Nvidia::NvCore
+
+namespace Service::android {
+
+class BufferItem;
+class BufferQueueCore;
+class IConsumerListener;
+
+class BufferQueueConsumer final {
+public:
+ explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_,
+ Service::Nvidia::NvCore::NvMap& nvmap_);
+ ~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;
+ Service::Nvidia::NvCore::NvMap& nvmap;
+};
+
+} // 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..ea4a14ea4
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_core.cpp
@@ -0,0 +1,113 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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();
+
+ 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..ca6baefaf
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_core.h
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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..334445213
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_defs.h
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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..d4ab23a10
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp
@@ -0,0 +1,927 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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/core/nvmap.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::Nvidia::NvCore::NvMap& nvmap_)
+ : service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots),
+ nvmap(nvmap_) {
+ 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;
+
+ nvmap.DuplicateHandle(item.graphic_buffer->BufferId(), true);
+
+ 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:
+ ASSERT(false);
+ 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..0ba03a568
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_producer.h
@@ -0,0 +1,90 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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::Nvidia::NvCore {
+class NvMap;
+} // namespace Service::Nvidia::NvCore
+
+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_,
+ Service::Nvidia::NvCore::NvMap& nvmap_);
+ ~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;
+
+ Service::Nvidia::NvCore::NvMap& nvmap;
+};
+
+} // 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..0cd0e9964
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_slot.h
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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 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..67aa5dad6
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_transform_flags.h
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#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..5b9995854
--- /dev/null
+++ b/src/core/hle/service/nvflinger/consumer_base.cpp
@@ -0,0 +1,133 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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..90ba07f45
--- /dev/null
+++ b/src/core/hle/service/nvflinger/consumer_base.h
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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..74a193988
--- /dev/null
+++ b/src/core/hle/service/nvflinger/consumer_listener.h
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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..4043c91f1
--- /dev/null
+++ b/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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..6ea327bbe
--- /dev/null
+++ b/src/core/hle/service/nvflinger/graphic_buffer_producer.h
@@ -0,0 +1,76 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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..dc9b2a9ec
--- /dev/null
+++ b/src/core/hle/service/nvflinger/hos_binder_driver_server.cpp
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#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..8fddc1206
--- /dev/null
+++ b/src/core/hle/service/nvflinger/hos_binder_driver_server.h
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#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..aa14d2cbc 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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <optional>
@@ -16,11 +15,17 @@
#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 "core/hle/service/vi/vi_results.h"
#include "video_core/gpu.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/host1x/syncpoint_manager.h"
namespace Service::NVFlinger {
@@ -28,7 +33,7 @@ constexpr auto frame_ns = std::chrono::nanoseconds{1000000000 / 60};
void NVFlinger::SplitVSync(std::stop_token stop_token) {
system.RegisterHostThread();
- std::string name = "yuzu:VSyncThread";
+ std::string name = "VSyncThread";
MicroProfileOnThreadCreate(name.c_str());
// Cleanup
@@ -36,69 +41,87 @@ void NVFlinger::SplitVSync(std::stop_token stop_token) {
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
- s64 delay = 0;
+
while (!stop_token.stop_requested()) {
+ vsync_signal.wait(false);
+ vsync_signal.store(false);
+
guard->lock();
- const s64 time_start = system.CoreTiming().GetGlobalTimeNs().count();
+
Compose();
- const auto ticks = GetNextTicks();
- const s64 time_end = system.CoreTiming().GetGlobalTimeNs().count();
- const s64 time_passed = time_end - time_start;
- const s64 next_time = std::max<s64>(0, ticks - time_passed - delay);
+
guard->unlock();
- if (next_time > 0) {
- std::this_thread::sleep_for(std::chrono::nanoseconds{next_time});
- }
- delay = (system.CoreTiming().GetGlobalTimeNs().count() - time_end) - next_time;
}
}
-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
- composition_event = Core::Timing::CreateEvent(
- "ScreenComposition", [this](std::uintptr_t, std::chrono::nanoseconds ns_late) {
+ multi_composition_event = Core::Timing::CreateEvent(
+ "ScreenComposition",
+ [this](std::uintptr_t, s64 time,
+ std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
+ vsync_signal.store(true);
+ vsync_signal.notify_all();
+ return std::chrono::nanoseconds(GetNextTicks());
+ });
+
+ single_composition_event = Core::Timing::CreateEvent(
+ "ScreenComposition",
+ [this](std::uintptr_t, s64 time,
+ std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto lock_guard = Lock();
Compose();
- const auto ticks = std::chrono::nanoseconds{GetNextTicks()};
- const auto ticks_delta = ticks - ns_late;
- const auto future_ns = std::max(std::chrono::nanoseconds::zero(), ticks_delta);
-
- this->system.CoreTiming().ScheduleEvent(future_ns, composition_event);
+ return std::chrono::nanoseconds(GetNextTicks());
});
if (system.IsMulticore()) {
+ system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event);
vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); });
} else {
- system.CoreTiming().ScheduleEvent(frame_ns, composition_event);
+ system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event);
}
}
NVFlinger::~NVFlinger() {
- for (auto& buffer_queue : buffer_queues) {
- buffer_queue->Disconnect();
+ if (system.IsMulticore()) {
+ system.CoreTiming().UnscheduleEvent(multi_composition_event, {});
+ vsync_thread.request_stop();
+ vsync_signal.store(true);
+ vsync_signal.notify_all();
+ } else {
+ system.CoreTiming().UnscheduleEvent(single_composition_event, {});
}
- 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();
+ }
+ }
+
+ if (nvdrv) {
+ nvdrv->Close(disp_fd);
}
}
void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
nvdrv = std::move(instance);
+ disp_fd = nvdrv->Open("/dev/nvdisp_disp0");
}
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 +148,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, nvdrv->container);
}
void NVFlinger::CloseLayer(u64 layer_id) {
@@ -147,30 +168,18 @@ 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) {
+ResultVal<Kernel::KReadableEvent*> NVFlinger::FindVsyncEvent(u64 display_id) {
const auto lock_guard = Lock();
auto* const display = FindDisplay(display_id);
if (display == nullptr) {
- return nullptr;
+ return VI::ResultNotFound;
}
- 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();
+ return display->GetVSyncEvent();
}
VI::Display* NVFlinger::FindDisplay(u64 display_id) {
@@ -227,7 +236,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,44 +255,43 @@ 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;
- guard->unlock();
- for (u32 fence_id = 0; fence_id < multi_fence.num_fences; fence_id++) {
- const auto& fence = multi_fence.fences[fence_id];
- gpu.WaitFence(fence.id, fence.value);
- }
- guard->lock();
-
- MicroProfileFlip();
-
// Now send the buffer to the GPU for drawing.
// TODO(Subv): Support more than just disp0. The display device selection is probably based
// on which display we're drawing (Default, Internal, External, etc)
- auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0");
+ auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd);
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);
+ guard->unlock();
+ 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())};
- swap_interval = buffer->get().swap_interval;
- buffer_queue.ReleaseBuffer(buffer->get().slot);
+ 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,
+ buffer.fence.fences, buffer.fence.num_fences);
+
+ MicroProfileFlip();
+ guard->lock();
+
+ swap_interval = buffer.swap_interval;
+
+ auto fence = android::Fence::NoFence();
+ layer.GetConsumer().ReleaseBuffer(buffer, fence);
}
}
@@ -291,9 +299,21 @@ s64 NVFlinger::GetNextTicks() const {
static constexpr s64 max_hertz = 120LL;
const auto& settings = Settings::values;
- const bool unlocked_fps = settings.disable_fps_limit.GetValue();
- const s64 fps_cap = unlocked_fps ? static_cast<s64>(settings.fps_cap.GetValue()) : 1;
- return (1000000000 * (1LL << swap_interval)) / (max_hertz * fps_cap);
+ auto speed_scale = 1.f;
+ if (settings.use_multi_core.GetValue()) {
+ if (settings.use_speed_limit.GetValue()) {
+ // Scales the speed based on speed_limit setting on MC. SC is handled by
+ // SpeedLimiter::DoSpeedLimiting.
+ speed_scale = 100.f / settings.speed_limit.GetValue();
+ } else {
+ // Run at unlocked framerate.
+ speed_scale = 0.01f;
+ }
+ }
+
+ const auto next_ticks = ((1000000000 * (1LL << swap_interval)) / max_hertz);
+
+ return static_cast<s64>(speed_scale * static_cast<float>(next_ticks));
}
} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index 7935cf773..b62615de2 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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -12,6 +11,7 @@
#include <vector>
#include "common/common_types.h"
+#include "core/hle/result.h"
#include "core/hle/service/kernel_helpers.h"
namespace Common {
@@ -37,13 +37,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.
@@ -69,11 +72,9 @@ public:
/// Gets the vsync event for the specified display.
///
- /// 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);
+ /// If an invalid display ID is provided, then VI::ResultNotFound is returned.
+ /// If the vsync event has already been retrieved, then VI::ResultPermissionDenied is returned.
+ [[nodiscard]] ResultVal<Kernel::KReadableEvent*> FindVsyncEvent(u64 display_id);
/// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
/// finished.
@@ -82,6 +83,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};
}
@@ -109,9 +116,9 @@ private:
void SplitVSync(std::stop_token stop_token);
std::shared_ptr<Nvidia::Module> nvdrv;
+ s32 disp_fd;
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;
@@ -122,15 +129,20 @@ private:
u32 swap_interval = 1;
/// Event that handles screen composition.
- std::shared_ptr<Core::Timing::EventType> composition_event;
+ std::shared_ptr<Core::Timing::EventType> multi_composition_event;
+ std::shared_ptr<Core::Timing::EventType> single_composition_event;
std::shared_ptr<std::mutex> guard;
Core::System& system;
+ std::atomic<bool> vsync_signal;
+
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..f3fa2587d
--- /dev/null
+++ b/src/core/hle/service/nvflinger/parcel.h
@@ -0,0 +1,172 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#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..f77d0acfb
--- /dev/null
+++ b/src/core/hle/service/nvflinger/pixel_format.h
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#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..1c4d5db0e
--- /dev/null
+++ b/src/core/hle/service/nvflinger/producer_listener.h
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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..7af166c40
--- /dev/null
+++ b/src/core/hle/service/nvflinger/status.h
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#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..536e8156d
--- /dev/null
+++ b/src/core/hle/service/nvflinger/ui/fence.h
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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..9a27f8f02
--- /dev/null
+++ b/src/core/hle/service/nvflinger/ui/graphic_buffer.h
@@ -0,0 +1,100 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2007 The Android Open Source Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Parts of this implementation were based 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..61cca5b01
--- /dev/null
+++ b/src/core/hle/service/nvflinger/window.h
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#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/olsc/olsc.cpp b/src/core/hle/service/olsc/olsc.cpp
index 39a8031a5..530e1be3b 100644
--- a/src/core/hle/service/olsc/olsc.cpp
+++ b/src/core/hle/service/olsc/olsc.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/olsc/olsc.h"
diff --git a/src/core/hle/service/olsc/olsc.h b/src/core/hle/service/olsc/olsc.h
index 24f24ca6b..1522d8d32 100644
--- a/src/core/hle/service/olsc/olsc.h
+++ b/src/core/hle/service/olsc/olsc.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/pcie/pcie.cpp b/src/core/hle/service/pcie/pcie.cpp
index 9bc851591..79501b9f9 100644
--- a/src/core/hle/service/pcie/pcie.cpp
+++ b/src/core/hle/service/pcie/pcie.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
diff --git a/src/core/hle/service/pcie/pcie.h b/src/core/hle/service/pcie/pcie.h
index e5709a72f..cebfd9042 100644
--- a/src/core/hle/service/pcie/pcie.h
+++ b/src/core/hle/service/pcie/pcie.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/pctl/pctl.cpp b/src/core/hle/service/pctl/pctl.cpp
index 908e0a1e3..3f47bf094 100644
--- a/src/core/hle/service/pctl/pctl.cpp
+++ b/src/core/hle/service/pctl/pctl.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/pctl/pctl.h"
diff --git a/src/core/hle/service/pctl/pctl.h b/src/core/hle/service/pctl/pctl.h
index 1d28900b2..87f93161e 100644
--- a/src/core/hle/service/pctl/pctl.h
+++ b/src/core/hle/service/pctl/pctl.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp
index 240776101..2a123b42d 100644
--- a/src/core/hle/service/pctl/pctl_module.cpp
+++ b/src/core/hle/service/pctl/pctl_module.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/core.h"
@@ -14,10 +13,10 @@ namespace Service::PCTL {
namespace Error {
-constexpr ResultCode ResultNoFreeCommunication{ErrorModule::PCTL, 101};
-constexpr ResultCode ResultStereoVisionRestricted{ErrorModule::PCTL, 104};
-constexpr ResultCode ResultNoCapability{ErrorModule::PCTL, 131};
-constexpr ResultCode ResultNoRestrictionEnabled{ErrorModule::PCTL, 181};
+constexpr Result ResultNoFreeCommunication{ErrorModule::PCTL, 101};
+constexpr Result ResultStereoVisionRestricted{ErrorModule::PCTL, 104};
+constexpr Result ResultNoCapability{ErrorModule::PCTL, 131};
+constexpr Result ResultNoRestrictionEnabled{ErrorModule::PCTL, 181};
} // namespace Error
diff --git a/src/core/hle/service/pctl/pctl_module.h b/src/core/hle/service/pctl/pctl_module.h
index f25c5c557..6f584530d 100644
--- a/src/core/hle/service/pctl/pctl_module.h
+++ b/src/core/hle/service/pctl/pctl_module.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/pcv/pcv.cpp b/src/core/hle/service/pcv/pcv.cpp
index 68b2c4178..f7a497a14 100644
--- a/src/core/hle/service/pcv/pcv.cpp
+++ b/src/core/hle/service/pcv/pcv.cpp
@@ -1,9 +1,9 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
+#include "core/hle/ipc_helpers.h"
#include "core/hle/service/pcv/pcv.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
@@ -78,10 +78,102 @@ public:
}
};
+class IClkrstSession final : public ServiceFramework<IClkrstSession> {
+public:
+ explicit IClkrstSession(Core::System& system_, DeviceCode deivce_code_)
+ : ServiceFramework{system_, "IClkrstSession"}, deivce_code(deivce_code_) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "SetClockEnabled"},
+ {1, nullptr, "SetClockDisabled"},
+ {2, nullptr, "SetResetAsserted"},
+ {3, nullptr, "SetResetDeasserted"},
+ {4, nullptr, "SetPowerEnabled"},
+ {5, nullptr, "SetPowerDisabled"},
+ {6, nullptr, "GetState"},
+ {7, &IClkrstSession::SetClockRate, "SetClockRate"},
+ {8, &IClkrstSession::GetClockRate, "GetClockRate"},
+ {9, nullptr, "SetMinVClockRate"},
+ {10, nullptr, "GetPossibleClockRates"},
+ {11, nullptr, "GetDvfsTable"},
+ };
+ // clang-format on
+ RegisterHandlers(functions);
+ }
+
+private:
+ void SetClockRate(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ clock_rate = rp.Pop<u32>();
+ LOG_DEBUG(Service_PCV, "(STUBBED) called, clock_rate={}", clock_rate);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void GetClockRate(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_PCV, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u32>(clock_rate);
+ }
+
+ DeviceCode deivce_code;
+ u32 clock_rate{};
+};
+
+class CLKRST final : public ServiceFramework<CLKRST> {
+public:
+ explicit CLKRST(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &CLKRST::OpenSession, "OpenSession"},
+ {1, nullptr, "GetTemperatureThresholds"},
+ {2, nullptr, "SetTemperature"},
+ {3, nullptr, "GetModuleStateTable"},
+ {4, nullptr, "GetModuleStateTableEvent"},
+ {5, nullptr, "GetModuleStateTableMaxCount"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void OpenSession(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_code = static_cast<DeviceCode>(rp.Pop<u32>());
+ const auto unkonwn_input = rp.Pop<u32>();
+
+ LOG_DEBUG(Service_PCV, "called, device_code={}, input={}", device_code, unkonwn_input);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IClkrstSession>(system, device_code);
+ }
+};
+
+class CLKRST_A final : public ServiceFramework<CLKRST_A> {
+public:
+ explicit CLKRST_A(Core::System& system_) : ServiceFramework{system_, "clkrst:a"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "ReleaseControl"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
std::make_shared<PCV>(system)->InstallAsService(sm);
std::make_shared<PCV_ARB>(system)->InstallAsService(sm);
std::make_shared<PCV_IMM>(system)->InstallAsService(sm);
+ std::make_shared<CLKRST>(system, "clkrst")->InstallAsService(sm);
+ std::make_shared<CLKRST>(system, "clkrst:i")->InstallAsService(sm);
+ std::make_shared<CLKRST_A>(system)->InstallAsService(sm);
}
} // namespace Service::PCV
diff --git a/src/core/hle/service/pcv/pcv.h b/src/core/hle/service/pcv/pcv.h
index c61a0b591..6b26b6fa7 100644
--- a/src/core/hle/service/pcv/pcv.h
+++ b/src/core/hle/service/pcv/pcv.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -14,6 +13,97 @@ class ServiceManager;
namespace Service::PCV {
+enum class DeviceCode : u32 {
+ Cpu = 0x40000001,
+ Gpu = 0x40000002,
+ I2s1 = 0x40000003,
+ I2s2 = 0x40000004,
+ I2s3 = 0x40000005,
+ Pwm = 0x40000006,
+ I2c1 = 0x02000001,
+ I2c2 = 0x02000002,
+ I2c3 = 0x02000003,
+ I2c4 = 0x02000004,
+ I2c5 = 0x02000005,
+ I2c6 = 0x02000006,
+ Spi1 = 0x07000000,
+ Spi2 = 0x07000001,
+ Spi3 = 0x07000002,
+ Spi4 = 0x07000003,
+ Disp1 = 0x40000011,
+ Disp2 = 0x40000012,
+ Isp = 0x40000013,
+ Vi = 0x40000014,
+ Sdmmc1 = 0x40000015,
+ Sdmmc2 = 0x40000016,
+ Sdmmc3 = 0x40000017,
+ Sdmmc4 = 0x40000018,
+ Owr = 0x40000019,
+ Csite = 0x4000001A,
+ Tsec = 0x4000001B,
+ Mselect = 0x4000001C,
+ Hda2codec2x = 0x4000001D,
+ Actmon = 0x4000001E,
+ I2cSlow = 0x4000001F,
+ Sor1 = 0x40000020,
+ Sata = 0x40000021,
+ Hda = 0x40000022,
+ XusbCoreHostSrc = 0x40000023,
+ XusbFalconSrc = 0x40000024,
+ XusbFsSrc = 0x40000025,
+ XusbCoreDevSrc = 0x40000026,
+ XusbSsSrc = 0x40000027,
+ UartA = 0x03000001,
+ UartB = 0x35000405,
+ UartC = 0x3500040F,
+ UartD = 0x37000001,
+ Host1x = 0x4000002C,
+ Entropy = 0x4000002D,
+ SocTherm = 0x4000002E,
+ Vic = 0x4000002F,
+ Nvenc = 0x40000030,
+ Nvjpg = 0x40000031,
+ Nvdec = 0x40000032,
+ Qspi = 0x40000033,
+ ViI2c = 0x40000034,
+ Tsecb = 0x40000035,
+ Ape = 0x40000036,
+ AudioDsp = 0x40000037,
+ AudioUart = 0x40000038,
+ Emc = 0x40000039,
+ Plle = 0x4000003A,
+ PlleHwSeq = 0x4000003B,
+ Dsi = 0x4000003C,
+ Maud = 0x4000003D,
+ Dpaux1 = 0x4000003E,
+ MipiCal = 0x4000003F,
+ UartFstMipiCal = 0x40000040,
+ Osc = 0x40000041,
+ SysBus = 0x40000042,
+ SorSafe = 0x40000043,
+ XusbSs = 0x40000044,
+ XusbHost = 0x40000045,
+ XusbDevice = 0x40000046,
+ Extperiph1 = 0x40000047,
+ Ahub = 0x40000048,
+ Hda2hdmicodec = 0x40000049,
+ Gpuaux = 0x4000004A,
+ UsbD = 0x4000004B,
+ Usb2 = 0x4000004C,
+ Pcie = 0x4000004D,
+ Afi = 0x4000004E,
+ PciExClk = 0x4000004F,
+ PExUsbPhy = 0x40000050,
+ XUsbPadCtl = 0x40000051,
+ Apbdma = 0x40000052,
+ Usb2TrkClk = 0x40000053,
+ XUsbIoPll = 0x40000054,
+ XUsbIoPllHwSeq = 0x40000055,
+ Cec = 0x40000056,
+ Extperiph2 = 0x40000057,
+ OscClk = 0x40000080
+};
+
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::PCV
diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp
index 057666021..b10e86c8f 100644
--- a/src/core/hle/service/pm/pm.cpp
+++ b/src/core/hle/service/pm/pm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
@@ -13,12 +12,12 @@ namespace Service::PM {
namespace {
-constexpr ResultCode ResultProcessNotFound{ErrorModule::PM, 1};
-[[maybe_unused]] constexpr ResultCode ResultAlreadyStarted{ErrorModule::PM, 2};
-[[maybe_unused]] constexpr ResultCode ResultNotTerminated{ErrorModule::PM, 3};
-[[maybe_unused]] constexpr ResultCode ResultDebugHookInUse{ErrorModule::PM, 4};
-[[maybe_unused]] constexpr ResultCode ResultApplicationRunning{ErrorModule::PM, 5};
-[[maybe_unused]] constexpr ResultCode ResultInvalidSize{ErrorModule::PM, 6};
+constexpr Result ResultProcessNotFound{ErrorModule::PM, 1};
+[[maybe_unused]] constexpr Result ResultAlreadyStarted{ErrorModule::PM, 2};
+[[maybe_unused]] constexpr Result ResultNotTerminated{ErrorModule::PM, 3};
+[[maybe_unused]] constexpr Result ResultDebugHookInUse{ErrorModule::PM, 4};
+[[maybe_unused]] constexpr Result ResultApplicationRunning{ErrorModule::PM, 5};
+[[maybe_unused]] constexpr Result ResultInvalidSize{ErrorModule::PM, 6};
constexpr u64 NO_PROCESS_FOUND_PID{0};
diff --git a/src/core/hle/service/pm/pm.h b/src/core/hle/service/pm/pm.h
index 852e7050c..060103928 100644
--- a/src/core/hle/service/pm/pm.h
+++ b/src/core/hle/service/pm/pm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp
index 5c8a44688..78f897d3e 100644
--- a/src/core/hle/service/prepo/prepo.cpp
+++ b/src/core/hle/service/prepo/prepo.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/hex_util.h"
#include "common/logging/log.h"
diff --git a/src/core/hle/service/prepo/prepo.h b/src/core/hle/service/prepo/prepo.h
index 395b57ead..37ea5afad 100644
--- a/src/core/hle/service/prepo/prepo.h
+++ b/src/core/hle/service/prepo/prepo.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/psc/psc.cpp b/src/core/hle/service/psc/psc.cpp
index f8ea02b58..3a9412cf5 100644
--- a/src/core/hle/service/psc/psc.cpp
+++ b/src/core/hle/service/psc/psc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
diff --git a/src/core/hle/service/psc/psc.h b/src/core/hle/service/psc/psc.h
index 89344f32d..d248372c2 100644
--- a/src/core/hle/service/psc/psc.h
+++ b/src/core/hle/service/psc/psc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/ptm/psm.cpp b/src/core/hle/service/ptm/psm.cpp
index 5d248f671..2c31e9485 100644
--- a/src/core/hle/service/ptm/psm.cpp
+++ b/src/core/hle/service/ptm/psm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
@@ -10,10 +9,8 @@
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/ptm/psm.h"
-#include "core/hle/service/service.h"
-#include "core/hle/service/sm/sm.h"
-namespace Service::PSM {
+namespace Service::PTM {
class IPsmSession final : public ServiceFramework<IPsmSession> {
public:
@@ -58,7 +55,7 @@ public:
private:
void BindStateChangeEvent(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_PSM, "called");
+ LOG_DEBUG(Service_PTM, "called");
should_signal = true;
@@ -68,7 +65,7 @@ private:
}
void UnbindStateChangeEvent(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_PSM, "called");
+ LOG_DEBUG(Service_PTM, "called");
should_signal = false;
@@ -79,7 +76,7 @@ private:
void SetChargerTypeChangeEventEnabled(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto state = rp.Pop<bool>();
- LOG_DEBUG(Service_PSM, "called, state={}", state);
+ LOG_DEBUG(Service_PTM, "called, state={}", state);
should_signal_charger_type = state;
@@ -90,7 +87,7 @@ private:
void SetPowerSupplyChangeEventEnabled(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto state = rp.Pop<bool>();
- LOG_DEBUG(Service_PSM, "called, state={}", state);
+ LOG_DEBUG(Service_PTM, "called, state={}", state);
should_signal_power_supply = state;
@@ -101,7 +98,7 @@ private:
void SetBatteryVoltageStateChangeEventEnabled(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto state = rp.Pop<bool>();
- LOG_DEBUG(Service_PSM, "called, state={}", state);
+ LOG_DEBUG(Service_PTM, "called, state={}", state);
should_signal_battery_voltage = state;
@@ -118,76 +115,58 @@ private:
Kernel::KEvent* state_change_event;
};
-class PSM final : public ServiceFramework<PSM> {
-public:
- explicit PSM(Core::System& system_) : ServiceFramework{system_, "psm"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &PSM::GetBatteryChargePercentage, "GetBatteryChargePercentage"},
- {1, &PSM::GetChargerType, "GetChargerType"},
- {2, nullptr, "EnableBatteryCharging"},
- {3, nullptr, "DisableBatteryCharging"},
- {4, nullptr, "IsBatteryChargingEnabled"},
- {5, nullptr, "AcquireControllerPowerSupply"},
- {6, nullptr, "ReleaseControllerPowerSupply"},
- {7, &PSM::OpenSession, "OpenSession"},
- {8, nullptr, "EnableEnoughPowerChargeEmulation"},
- {9, nullptr, "DisableEnoughPowerChargeEmulation"},
- {10, nullptr, "EnableFastBatteryCharging"},
- {11, nullptr, "DisableFastBatteryCharging"},
- {12, nullptr, "GetBatteryVoltageState"},
- {13, nullptr, "GetRawBatteryChargePercentage"},
- {14, nullptr, "IsEnoughPowerSupplied"},
- {15, nullptr, "GetBatteryAgePercentage"},
- {16, nullptr, "GetBatteryChargeInfoEvent"},
- {17, nullptr, "GetBatteryChargeInfoFields"},
- {18, nullptr, "GetBatteryChargeCalibratedEvent"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
- ~PSM() override = default;
-
-private:
- void GetBatteryChargePercentage(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_PSM, "called");
+PSM::PSM(Core::System& system_) : ServiceFramework{system_, "psm"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &PSM::GetBatteryChargePercentage, "GetBatteryChargePercentage"},
+ {1, &PSM::GetChargerType, "GetChargerType"},
+ {2, nullptr, "EnableBatteryCharging"},
+ {3, nullptr, "DisableBatteryCharging"},
+ {4, nullptr, "IsBatteryChargingEnabled"},
+ {5, nullptr, "AcquireControllerPowerSupply"},
+ {6, nullptr, "ReleaseControllerPowerSupply"},
+ {7, &PSM::OpenSession, "OpenSession"},
+ {8, nullptr, "EnableEnoughPowerChargeEmulation"},
+ {9, nullptr, "DisableEnoughPowerChargeEmulation"},
+ {10, nullptr, "EnableFastBatteryCharging"},
+ {11, nullptr, "DisableFastBatteryCharging"},
+ {12, nullptr, "GetBatteryVoltageState"},
+ {13, nullptr, "GetRawBatteryChargePercentage"},
+ {14, nullptr, "IsEnoughPowerSupplied"},
+ {15, nullptr, "GetBatteryAgePercentage"},
+ {16, nullptr, "GetBatteryChargeInfoEvent"},
+ {17, nullptr, "GetBatteryChargeInfoFields"},
+ {18, nullptr, "GetBatteryChargeCalibratedEvent"},
+ };
+ // clang-format on
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(battery_charge_percentage);
- }
+ RegisterHandlers(functions);
+}
- void GetChargerType(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_PSM, "called");
+PSM::~PSM() = default;
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(charger_type);
- }
+void PSM::GetBatteryChargePercentage(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_PTM, "called");
- void OpenSession(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_PSM, "called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u32>(battery_charge_percentage);
+}
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IPsmSession>(system);
- }
+void PSM::GetChargerType(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_PTM, "called");
- enum class ChargerType : u32 {
- Unplugged = 0,
- RegularCharger = 1,
- LowPowerCharger = 2,
- Unknown = 3,
- };
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(charger_type);
+}
- u32 battery_charge_percentage{100}; // 100%
- ChargerType charger_type{ChargerType::RegularCharger};
-};
+void PSM::OpenSession(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_PTM, "called");
-void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
- std::make_shared<PSM>(system)->InstallAsService(sm);
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IPsmSession>(system);
}
-} // namespace Service::PSM
+} // namespace Service::PTM
diff --git a/src/core/hle/service/ptm/psm.h b/src/core/hle/service/ptm/psm.h
index 2930ce26a..f674ba8bc 100644
--- a/src/core/hle/service/ptm/psm.h
+++ b/src/core/hle/service/ptm/psm.h
@@ -1,19 +1,31 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-namespace Core {
-class System;
-}
+#include "core/hle/service/service.h"
-namespace Service::SM {
-class ServiceManager;
-}
+namespace Service::PTM {
-namespace Service::PSM {
+class PSM final : public ServiceFramework<PSM> {
+public:
+ explicit PSM(Core::System& system_);
+ ~PSM() override;
-void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
+private:
+ enum class ChargerType : u32 {
+ Unplugged = 0,
+ RegularCharger = 1,
+ LowPowerCharger = 2,
+ Unknown = 3,
+ };
-} // namespace Service::PSM
+ void GetBatteryChargePercentage(Kernel::HLERequestContext& ctx);
+ void GetChargerType(Kernel::HLERequestContext& ctx);
+ void OpenSession(Kernel::HLERequestContext& ctx);
+
+ u32 battery_charge_percentage{100};
+ ChargerType charger_type{ChargerType::RegularCharger};
+};
+
+} // namespace Service::PTM
diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp
new file mode 100644
index 000000000..4bea995c6
--- /dev/null
+++ b/src/core/hle/service/ptm/ptm.cpp
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <memory>
+
+#include "core/core.h"
+#include "core/hle/service/ptm/psm.h"
+#include "core/hle/service/ptm/ptm.h"
+#include "core/hle/service/ptm/ts.h"
+
+namespace Service::PTM {
+
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<PSM>(system)->InstallAsService(sm);
+ std::make_shared<TS>(system)->InstallAsService(sm);
+}
+
+} // namespace Service::PTM
diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h
new file mode 100644
index 000000000..06224a24e
--- /dev/null
+++ b/src/core/hle/service/ptm/ptm.h
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+namespace Core {
+class System;
+}
+
+namespace Service::SM {
+class ServiceManager;
+}
+
+namespace Service::PTM {
+
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
+
+} // namespace Service::PTM
diff --git a/src/core/hle/service/ptm/ts.cpp b/src/core/hle/service/ptm/ts.cpp
new file mode 100644
index 000000000..65c3f135f
--- /dev/null
+++ b/src/core/hle/service/ptm/ts.cpp
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <memory>
+
+#include "core/core.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/ptm/ts.h"
+
+namespace Service::PTM {
+
+TS::TS(Core::System& system_) : ServiceFramework{system_, "ts"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetTemperatureRange"},
+ {1, &TS::GetTemperature, "GetTemperature"},
+ {2, nullptr, "SetMeasurementMode"},
+ {3, nullptr, "GetTemperatureMilliC"},
+ {4, nullptr, "OpenSession"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+TS::~TS() = default;
+
+void TS::GetTemperature(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto location{rp.PopEnum<Location>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called. location={}", location);
+
+ const s32 temperature = location == Location::Internal ? 35 : 20;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(temperature);
+}
+
+} // namespace Service::PTM
diff --git a/src/core/hle/service/ptm/ts.h b/src/core/hle/service/ptm/ts.h
new file mode 100644
index 000000000..39a734ef7
--- /dev/null
+++ b/src/core/hle/service/ptm/ts.h
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/service/service.h"
+
+namespace Service::PTM {
+
+class TS final : public ServiceFramework<TS> {
+public:
+ explicit TS(Core::System& system_);
+ ~TS() override;
+
+private:
+ enum class Location : u8 {
+ Internal,
+ External,
+ };
+
+ void GetTemperature(Kernel::HLERequestContext& ctx);
+};
+
+} // namespace Service::PTM
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index f54e6fe56..dadaf897f 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/format.h>
#include "common/assert.h"
@@ -32,6 +31,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"
@@ -39,6 +39,7 @@
#include "core/hle/service/mig/mig.h"
#include "core/hle/service/mii/mii.h"
#include "core/hle/service/mm/mm_u.h"
+#include "core/hle/service/mnpp/mnpp_app.h"
#include "core/hle/service/ncm/ncm.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/nfp/nfp.h"
@@ -48,6 +49,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"
@@ -56,7 +58,7 @@
#include "core/hle/service/pm/pm.h"
#include "core/hle/service/prepo/prepo.h"
#include "core/hle/service/psc/psc.h"
-#include "core/hle/service/ptm/psm.h"
+#include "core/hle/service/ptm/ptm.h"
#include "core/hle/service/service.h"
#include "core/hle/service/set/settings.h"
#include "core/hle/service/sm/sm.h"
@@ -89,8 +91,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() {
@@ -187,17 +190,20 @@ void ServiceFrameworkBase::InvokeRequestTipc(Kernel::HLERequestContext& ctx) {
handler_invoker(this, info->handler_callback, ctx);
}
-ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& session,
- Kernel::HLERequestContext& ctx) {
+Result ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& session,
+ Kernel::HLERequestContext& ctx) {
const auto guard = LockService();
+ Result result = ResultSuccess;
+
switch (ctx.GetCommandType()) {
case IPC::CommandType::Close:
case IPC::CommandType::TIPC_Close: {
session.Close();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
- return IPC::ERR_REMOTE_PROCESS_DEAD;
+ result = IPC::ERR_REMOTE_PROCESS_DEAD;
+ break;
}
case IPC::CommandType::ControlWithContext:
case IPC::CommandType::Control: {
@@ -224,12 +230,13 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& sessi
ctx.WriteToOutgoingCommandBuffer(ctx.GetThread());
}
- return ResultSuccess;
+ return result;
}
/// 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.
@@ -258,6 +265,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);
@@ -265,6 +273,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
Migration::InstallInterfaces(*sm, system);
Mii::InstallInterfaces(*sm, system);
MM::InstallInterfaces(*sm, system);
+ MNPP::InstallInterfaces(*sm, system);
NCM::InstallInterfaces(*sm, system);
NFC::InstallInterfaces(*sm, system);
NFP::InstallInterfaces(*sm, system);
@@ -281,14 +290,14 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
PlayReport::InstallInterfaces(*sm, system);
PM::InstallInterfaces(system);
PSC::InstallInterfaces(*sm, system);
- PSM::InstallInterfaces(*sm, system);
+ PTM::InstallInterfaces(*sm, system);
Set::InstallInterfaces(*sm, system);
Sockets::InstallInterfaces(*sm, system);
SPL::InstallInterfaces(*sm, system);
SSL::InstallInterfaces(*sm, system);
Time::InstallInterfaces(system);
USB::InstallInterfaces(*sm, 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..5bf197c51 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -9,7 +8,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 +31,9 @@ class FileSystemController;
}
namespace NVFlinger {
+class HosBinderDriverServer;
class NVFlinger;
-}
+} // namespace NVFlinger
namespace SM {
class ServiceManager;
@@ -80,8 +79,8 @@ public:
Kernel::KClientPort& CreatePort();
/// Handles a synchronization request for the service.
- ResultCode HandleSyncRequest(Kernel::KServerSession& session,
- Kernel::HLERequestContext& context) override;
+ Result HandleSyncRequest(Kernel::KServerSession& session,
+ Kernel::HLERequestContext& context) override;
protected:
/// Member-function pointer type of SyncRequest handlers.
@@ -89,7 +88,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 +112,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 +133,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 +175,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 +239,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/set/set.cpp b/src/core/hle/service/set/set.cpp
index 8795eb6b7..f761c2da4 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -75,7 +74,7 @@ constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 18> language_to_la
constexpr std::size_t PRE_4_0_0_MAX_ENTRIES = 0xF;
constexpr std::size_t POST_4_0_0_MAX_ENTRIES = 0x40;
-constexpr ResultCode ERR_INVALID_LANGUAGE{ErrorModule::Settings, 625};
+constexpr Result ERR_INVALID_LANGUAGE{ErrorModule::Settings, 625};
void PushResponseLanguageCode(Kernel::HLERequestContext& ctx, std::size_t num_language_codes) {
IPC::ResponseBuilder rb{ctx, 3};
diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h
index acabebeaa..60cad3e6f 100644
--- a/src/core/hle/service/set/set.h
+++ b/src/core/hle/service/set/set.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/set/set_cal.cpp b/src/core/hle/service/set/set_cal.cpp
index b2aa7bc0c..d2c0d536f 100644
--- a/src/core/hle/service/set/set_cal.cpp
+++ b/src/core/hle/service/set/set_cal.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/set/set_cal.h"
diff --git a/src/core/hle/service/set/set_cal.h b/src/core/hle/service/set/set_cal.h
index a29fc3ddd..8f50278ed 100644
--- a/src/core/hle/service/set/set_cal.h
+++ b/src/core/hle/service/set/set_cal.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/set/set_fd.cpp b/src/core/hle/service/set/set_fd.cpp
index f04dc5047..278ef32e1 100644
--- a/src/core/hle/service/set/set_fd.cpp
+++ b/src/core/hle/service/set/set_fd.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/set/set_fd.h"
diff --git a/src/core/hle/service/set/set_fd.h b/src/core/hle/service/set/set_fd.h
index c28cb301e..150a7cbce 100644
--- a/src/core/hle/service/set/set_fd.h
+++ b/src/core/hle/service/set/set_fd.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index 38e6eae04..2a0b812c1 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/logging/log.h"
@@ -33,7 +32,7 @@ void GetFirmwareVersionImpl(Kernel::HLERequestContext& ctx, GetFirmwareVersionTy
// consistence (currently reports as 5.1.0-0.0)
const auto archive = FileSys::SystemArchive::SystemVersion();
- const auto early_exit_failure = [&ctx](std::string_view desc, ResultCode code) {
+ const auto early_exit_failure = [&ctx](std::string_view desc, Result code) {
LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).",
desc);
IPC::ResponseBuilder rb{ctx, 2};
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index edb185a68..ac97772b7 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/set/settings.cpp b/src/core/hle/service/set/settings.cpp
index 212ebc427..4ebc2a0ec 100644
--- a/src/core/hle/service/set/settings.cpp
+++ b/src/core/hle/service/set/settings.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/set/set.h"
#include "core/hle/service/set/set_cal.h"
diff --git a/src/core/hle/service/set/settings.h b/src/core/hle/service/set/settings.h
index 7a6950dd0..6cd7d634c 100644
--- a/src/core/hle/service/set/settings.h
+++ b/src/core/hle/service/set/settings.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index eaa172595..246c94623 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <tuple>
#include "common/assert.h"
@@ -18,10 +17,10 @@
namespace Service::SM {
-constexpr ResultCode ERR_NOT_INITIALIZED(ErrorModule::SM, 2);
-constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorModule::SM, 4);
-constexpr ResultCode ERR_INVALID_NAME(ErrorModule::SM, 6);
-constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7);
+constexpr Result ERR_NOT_INITIALIZED(ErrorModule::SM, 2);
+constexpr Result ERR_ALREADY_REGISTERED(ErrorModule::SM, 4);
+constexpr Result ERR_INVALID_NAME(ErrorModule::SM, 6);
+constexpr Result ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7);
ServiceManager::ServiceManager(Kernel::KernelCore& kernel_) : kernel{kernel_} {}
ServiceManager::~ServiceManager() = default;
@@ -30,7 +29,7 @@ void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
controller_interface->InvokeRequest(context);
}
-static ResultCode ValidateServiceName(const std::string& name) {
+static Result ValidateServiceName(const std::string& name) {
if (name.empty() || name.size() > 8) {
LOG_ERROR(Service_SM, "Invalid service name! service={}", name);
return ERR_INVALID_NAME;
@@ -44,8 +43,8 @@ Kernel::KClientPort& ServiceManager::InterfaceFactory(ServiceManager& self, Core
return self.sm_interface->CreatePort();
}
-ResultCode ServiceManager::RegisterService(std::string name, u32 max_sessions,
- Kernel::SessionRequestHandlerPtr handler) {
+Result ServiceManager::RegisterService(std::string name, u32 max_sessions,
+ Kernel::SessionRequestHandlerPtr handler) {
CASCADE_CODE(ValidateServiceName(name));
@@ -59,7 +58,7 @@ ResultCode ServiceManager::RegisterService(std::string name, u32 max_sessions,
return ResultSuccess;
}
-ResultCode ServiceManager::UnregisterService(const std::string& name) {
+Result ServiceManager::UnregisterService(const std::string& name) {
CASCADE_CODE(ValidateServiceName(name));
const auto iter = registered_services.find(name);
@@ -81,6 +80,8 @@ ResultVal<Kernel::KPort*> ServiceManager::GetServicePort(const std::string& name
}
auto* port = Kernel::KPort::Create(kernel);
+ SCOPE_EXIT({ port->Close(); });
+
port->Initialize(ServerSessionCountMax, false, name);
auto handler = it->second;
port->GetServerPort().SetSessionHandler(std::move(handler));
@@ -93,7 +94,7 @@ ResultVal<Kernel::KPort*> ServiceManager::GetServicePort(const std::string& name
* Inputs:
* 0: 0x00000000
* Outputs:
- * 0: ResultCode
+ * 0: Result
*/
void SM::Initialize(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_SM, "called");
@@ -151,7 +152,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{};
@@ -204,7 +205,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"},
@@ -222,10 +223,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..878decc6f 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -22,7 +21,6 @@ class KClientPort;
class KClientSession;
class KernelCore;
class KPort;
-class KServerPort;
class SessionRequestHandler;
} // namespace Kernel
@@ -48,7 +46,6 @@ private:
ServiceManager& service_manager;
bool is_initialized{};
Kernel::KernelCore& kernel;
- std::vector<Kernel::KServerPort*> server_ports;
};
class ServiceManager {
@@ -58,9 +55,9 @@ public:
explicit ServiceManager(Kernel::KernelCore& kernel_);
~ServiceManager();
- ResultCode RegisterService(std::string name, u32 max_sessions,
- Kernel::SessionRequestHandlerPtr handler);
- ResultCode UnregisterService(const std::string& name);
+ Result RegisterService(std::string name, u32 max_sessions,
+ Kernel::SessionRequestHandlerPtr handler);
+ Result UnregisterService(const std::string& name);
ResultVal<Kernel::KPort*> GetServicePort(const std::string& name);
template <Common::DerivedFrom<Kernel::SessionRequestHandler> T>
diff --git a/src/core/hle/service/sm/sm_controller.cpp b/src/core/hle/service/sm/sm_controller.cpp
index 09f9ecee1..2a4bd64ab 100644
--- a/src/core/hle/service/sm/sm_controller.cpp
+++ b/src/core/hle/service/sm/sm_controller.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/logging/log.h"
@@ -34,7 +33,7 @@ void Controller::CloneCurrentObject(Kernel::HLERequestContext& ctx) {
// Create a session.
Kernel::KClientSession* session{};
- const ResultCode result = parent_port.CreateSession(std::addressof(session), session_manager);
+ const Result result = parent_port.CreateSession(std::addressof(session), session_manager);
if (result.IsError()) {
LOG_CRITICAL(Service, "CreateSession failed with error 0x{:08X}", result.raw);
IPC::ResponseBuilder rb{ctx, 2};
diff --git a/src/core/hle/service/sm/sm_controller.h b/src/core/hle/service/sm/sm_controller.h
index 7494f898d..ed386f660 100644
--- a/src/core/hle/service/sm/sm_controller.h
+++ b/src/core/hle/service/sm/sm_controller.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index f83272633..9e94a462f 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <memory>
@@ -10,13 +9,16 @@
#include <fmt/format.h>
#include "common/microprofile.h"
-#include "common/thread.h"
+#include "common/socket_types.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/service/sockets/bsd.h"
#include "core/hle/service/sockets/sockets_translate.h"
-#include "core/network/network.h"
-#include "core/network/sockets.h"
+#include "core/internal_network/network.h"
+#include "core/internal_network/socket_proxy.h"
+#include "core/internal_network/sockets.h"
+#include "network/network.h"
namespace Service::Sockets {
@@ -474,7 +476,13 @@ std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protoco
LOG_INFO(Service, "New socket fd={}", fd);
- descriptor.socket = std::make_unique<Network::Socket>();
+ auto room_member = room_network.GetRoomMember().lock();
+ if (room_member && room_member->IsConnected()) {
+ descriptor.socket = std::make_unique<Network::ProxySocket>(room_network);
+ } else {
+ descriptor.socket = std::make_unique<Network::Socket>();
+ }
+
descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol));
descriptor.is_connection_based = IsConnectionBased(type);
@@ -569,9 +577,9 @@ std::pair<s32, Errno> BSD::AcceptImpl(s32 fd, std::vector<u8>& write_buffer) {
new_descriptor.socket = std::move(result.socket);
new_descriptor.is_connection_based = descriptor.is_connection_based;
- ASSERT(write_buffer.size() == sizeof(SockAddrIn));
const SockAddrIn guest_addr_in = Translate(result.sockaddr_in);
- std::memcpy(write_buffer.data(), &guest_addr_in, sizeof(guest_addr_in));
+ const size_t length = std::min(sizeof(guest_addr_in), write_buffer.size());
+ std::memcpy(write_buffer.data(), &guest_addr_in, length);
return {new_fd, Errno::SUCCESS};
}
@@ -650,7 +658,7 @@ std::pair<s32, Errno> BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) {
ASSERT(arg == 0);
return {descriptor.flags, Errno::SUCCESS};
case FcntlCmd::SETFL: {
- const bool enable = (arg & FLAG_O_NONBLOCK) != 0;
+ const bool enable = (arg & Network::FLAG_O_NONBLOCK) != 0;
const Errno bsd_errno = Translate(descriptor.socket->SetNonBlock(enable));
if (bsd_errno != Errno::SUCCESS) {
return {-1, bsd_errno};
@@ -671,7 +679,7 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, con
return Errno::BADF;
}
- Network::Socket* const socket = file_descriptors[fd]->socket.get();
+ Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
if (optname == OptName::LINGER) {
ASSERT(optlen == sizeof(Linger));
@@ -690,6 +698,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));
@@ -719,7 +730,27 @@ std::pair<s32, Errno> BSD::RecvImpl(s32 fd, u32 flags, std::vector<u8>& message)
if (!IsFileDescriptorValid(fd)) {
return {-1, Errno::BADF};
}
- return Translate(file_descriptors[fd]->socket->Recv(flags, message));
+
+ FileDescriptor& descriptor = *file_descriptors[fd];
+
+ // Apply flags
+ using Network::FLAG_MSG_DONTWAIT;
+ using Network::FLAG_O_NONBLOCK;
+ if ((flags & FLAG_MSG_DONTWAIT) != 0) {
+ flags &= ~FLAG_MSG_DONTWAIT;
+ if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
+ descriptor.socket->SetNonBlock(true);
+ }
+ }
+
+ const auto [ret, bsd_errno] = Translate(descriptor.socket->Recv(flags, message));
+
+ // Restore original state
+ if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
+ descriptor.socket->SetNonBlock(false);
+ }
+
+ return {ret, bsd_errno};
}
std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message,
@@ -740,6 +771,8 @@ std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& mess
}
// Apply flags
+ using Network::FLAG_MSG_DONTWAIT;
+ using Network::FLAG_O_NONBLOCK;
if ((flags & FLAG_MSG_DONTWAIT) != 0) {
flags &= ~FLAG_MSG_DONTWAIT;
if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
@@ -838,7 +871,19 @@ 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} {
+void BSD::OnProxyPacketReceived(const Network::ProxyPacket& packet) {
+ for (auto& optional_descriptor : file_descriptors) {
+ if (!optional_descriptor.has_value()) {
+ continue;
+ }
+ FileDescriptor& descriptor = *optional_descriptor;
+ descriptor.socket.get()->HandleProxyPacket(packet);
+ }
+}
+
+BSD::BSD(Core::System& system_, const char* name)
+ : ServiceFramework{system_, name, ServiceThreadType::CreateNew}, room_network{
+ system_.GetRoomNetwork()} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &BSD::RegisterClient, "RegisterClient"},
@@ -879,9 +924,20 @@ BSD::BSD(Core::System& system_, const char* name) : ServiceFramework{system_, na
// clang-format on
RegisterHandlers(functions);
+
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ proxy_packet_received = room_member->BindOnProxyPacketReceived(
+ [this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); });
+ } else {
+ LOG_ERROR(Service, "Network isn't initialized");
+ }
}
-BSD::~BSD() = default;
+BSD::~BSD() {
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ room_member->Unbind(proxy_packet_received);
+ }
+}
BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} {
// clang-format off
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
index a387e50df..81e855e0f 100644
--- a/src/core/hle/service/sockets/bsd.h
+++ b/src/core/hle/service/sockets/bsd.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,16 +7,19 @@
#include <vector>
#include "common/common_types.h"
+#include "common/socket_types.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sockets/sockets.h"
+#include "network/network.h"
namespace Core {
class System;
}
namespace Network {
+class SocketBase;
class Socket;
-}
+} // namespace Network
namespace Service::Sockets {
@@ -31,7 +33,7 @@ private:
static constexpr size_t MAX_FD = 128;
struct FileDescriptor {
- std::unique_ptr<Network::Socket> socket;
+ std::unique_ptr<Network::SocketBase> socket;
s32 flags = 0;
bool is_connection_based = false;
};
@@ -166,6 +168,14 @@ private:
void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept;
std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
+
+ Network::RoomNetwork& room_network;
+
+ /// Callback to parse and handle a received wifi packet.
+ void OnProxyPacketReceived(const Network::ProxyPacket& packet);
+
+ // Callback identifier for the OnProxyPacketReceived event.
+ Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received;
};
class BSDCFG final : public ServiceFramework<BSDCFG> {
diff --git a/src/core/hle/service/sockets/ethc.cpp b/src/core/hle/service/sockets/ethc.cpp
index 899a64c2f..c12ea999b 100644
--- a/src/core/hle/service/sockets/ethc.cpp
+++ b/src/core/hle/service/sockets/ethc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/sockets/ethc.h"
diff --git a/src/core/hle/service/sockets/ethc.h b/src/core/hle/service/sockets/ethc.h
index 71884182e..7c5759a96 100644
--- a/src/core/hle/service/sockets/ethc.h
+++ b/src/core/hle/service/sockets/ethc.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp
index 1159debc5..6491a73be 100644
--- a/src/core/hle/service/sockets/nsd.cpp
+++ b/src/core/hle/service/sockets/nsd.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/sockets/nsd.h"
diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h
index becf93125..5cc12b855 100644
--- a/src/core/hle/service/sockets/nsd.h
+++ b/src/core/hle/service/sockets/nsd.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp
index fb6142c49..097c37d7a 100644
--- a/src/core/hle/service/sockets/sfdnsres.cpp
+++ b/src/core/hle/service/sockets/sfdnsres.cpp
@@ -1,9 +1,28 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+#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 +40,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 +50,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 +196,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..96018ea77 100644
--- a/src/core/hle/service/sockets/sfdnsres.h
+++ b/src/core/hle/service/sockets/sfdnsres.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -19,6 +18,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.cpp b/src/core/hle/service/sockets/sockets.cpp
index 96f73bce3..8d3ba6f96 100644
--- a/src/core/hle/service/sockets/sockets.cpp
+++ b/src/core/hle/service/sockets/sockets.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/sockets/bsd.h"
#include "core/hle/service/sockets/ethc.h"
diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h
index 02dbbae40..31b7dad33 100644
--- a/src/core/hle/service/sockets/sockets.h
+++ b/src/core/hle/service/sockets/sockets.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -23,7 +22,9 @@ enum class Errno : u32 {
AGAIN = 11,
INVAL = 22,
MFILE = 24,
+ MSGSIZE = 90,
NOTCONN = 107,
+ TIMEDOUT = 110,
};
enum class Domain : u32 {
@@ -46,6 +47,7 @@ enum class Protocol : u32 {
enum class OptName : u32 {
REUSEADDR = 0x4,
+ KEEPALIVE = 0x8,
BROADCAST = 0x20,
LINGER = 0x80,
SNDBUF = 0x1001,
@@ -96,10 +98,6 @@ struct Linger {
u32 linger;
};
-constexpr u32 FLAG_MSG_DONTWAIT = 0x80;
-
-constexpr u32 FLAG_O_NONBLOCK = 0x800;
-
/// Registers all Sockets services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp
index ca61d72ca..023aa0486 100644
--- a/src/core/hle/service/sockets/sockets_translate.cpp
+++ b/src/core/hle/service/sockets/sockets_translate.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <utility>
@@ -8,7 +7,7 @@
#include "common/common_types.h"
#include "core/hle/service/sockets/sockets.h"
#include "core/hle/service/sockets/sockets_translate.h"
-#include "core/network/network.h"
+#include "core/internal_network/network.h"
namespace Service::Sockets {
@@ -26,6 +25,8 @@ Errno Translate(Network::Errno value) {
return Errno::MFILE;
case Network::Errno::NOTCONN:
return Errno::NOTCONN;
+ case Network::Errno::TIMEDOUT:
+ return Errno::TIMEDOUT;
default:
UNIMPLEMENTED_MSG("Unimplemented errno={}", value);
return Errno::SUCCESS;
diff --git a/src/core/hle/service/sockets/sockets_translate.h b/src/core/hle/service/sockets/sockets_translate.h
index 057d1ff22..c93291d3e 100644
--- a/src/core/hle/service/sockets/sockets_translate.h
+++ b/src/core/hle/service/sockets/sockets_translate.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,7 +7,7 @@
#include "common/common_types.h"
#include "core/hle/service/sockets/sockets.h"
-#include "core/network/network.h"
+#include "core/internal_network/network.h"
namespace Service::Sockets {
diff --git a/src/core/hle/service/spl/csrng.cpp b/src/core/hle/service/spl/csrng.cpp
index 9c7f89475..ca121fb05 100644
--- a/src/core/hle/service/spl/csrng.cpp
+++ b/src/core/hle/service/spl/csrng.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/spl/csrng.h"
diff --git a/src/core/hle/service/spl/csrng.h b/src/core/hle/service/spl/csrng.h
index 0d03cc6cb..b337a8281 100644
--- a/src/core/hle/service/spl/csrng.h
+++ b/src/core/hle/service/spl/csrng.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp
index 20384042f..fde212186 100644
--- a/src/core/hle/service/spl/spl.cpp
+++ b/src/core/hle/service/spl/spl.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/spl/spl.h"
diff --git a/src/core/hle/service/spl/spl.h b/src/core/hle/service/spl/spl.h
index 5599c0c01..7b63d8b1a 100644
--- a/src/core/hle/service/spl/spl.h
+++ b/src/core/hle/service/spl/spl.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/spl/spl_module.cpp b/src/core/hle/service/spl/spl_module.cpp
index 10f7d1461..64eae1ebf 100644
--- a/src/core/hle/service/spl/spl_module.cpp
+++ b/src/core/hle/service/spl/spl_module.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstdlib>
diff --git a/src/core/hle/service/spl/spl_module.h b/src/core/hle/service/spl/spl_module.h
index 61630df80..4c9a3c618 100644
--- a/src/core/hle/service/spl/spl_module.h
+++ b/src/core/hle/service/spl/spl_module.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/spl/spl_results.h b/src/core/hle/service/spl/spl_results.h
index a07c61409..dd7ba11f3 100644
--- a/src/core/hle/service/spl/spl_results.h
+++ b/src/core/hle/service/spl/spl_results.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -9,23 +8,23 @@
namespace Service::SPL {
// Description 0 - 99
-constexpr ResultCode ResultSecureMonitorError{ErrorModule::SPL, 0};
-constexpr ResultCode ResultSecureMonitorNotImplemented{ErrorModule::SPL, 1};
-constexpr ResultCode ResultSecureMonitorInvalidArgument{ErrorModule::SPL, 2};
-constexpr ResultCode ResultSecureMonitorBusy{ErrorModule::SPL, 3};
-constexpr ResultCode ResultSecureMonitorNoAsyncOperation{ErrorModule::SPL, 4};
-constexpr ResultCode ResultSecureMonitorInvalidAsyncOperation{ErrorModule::SPL, 5};
-constexpr ResultCode ResultSecureMonitorNotPermitted{ErrorModule::SPL, 6};
-constexpr ResultCode ResultSecureMonitorNotInitialized{ErrorModule::SPL, 7};
+constexpr Result ResultSecureMonitorError{ErrorModule::SPL, 0};
+constexpr Result ResultSecureMonitorNotImplemented{ErrorModule::SPL, 1};
+constexpr Result ResultSecureMonitorInvalidArgument{ErrorModule::SPL, 2};
+constexpr Result ResultSecureMonitorBusy{ErrorModule::SPL, 3};
+constexpr Result ResultSecureMonitorNoAsyncOperation{ErrorModule::SPL, 4};
+constexpr Result ResultSecureMonitorInvalidAsyncOperation{ErrorModule::SPL, 5};
+constexpr Result ResultSecureMonitorNotPermitted{ErrorModule::SPL, 6};
+constexpr Result ResultSecureMonitorNotInitialized{ErrorModule::SPL, 7};
-constexpr ResultCode ResultInvalidSize{ErrorModule::SPL, 100};
-constexpr ResultCode ResultUnknownSecureMonitorError{ErrorModule::SPL, 101};
-constexpr ResultCode ResultDecryptionFailed{ErrorModule::SPL, 102};
+constexpr Result ResultInvalidSize{ErrorModule::SPL, 100};
+constexpr Result ResultUnknownSecureMonitorError{ErrorModule::SPL, 101};
+constexpr Result ResultDecryptionFailed{ErrorModule::SPL, 102};
-constexpr ResultCode ResultOutOfKeySlots{ErrorModule::SPL, 104};
-constexpr ResultCode ResultInvalidKeySlot{ErrorModule::SPL, 105};
-constexpr ResultCode ResultBootReasonAlreadySet{ErrorModule::SPL, 106};
-constexpr ResultCode ResultBootReasonNotSet{ErrorModule::SPL, 107};
-constexpr ResultCode ResultInvalidArgument{ErrorModule::SPL, 108};
+constexpr Result ResultOutOfKeySlots{ErrorModule::SPL, 104};
+constexpr Result ResultInvalidKeySlot{ErrorModule::SPL, 105};
+constexpr Result ResultBootReasonAlreadySet{ErrorModule::SPL, 106};
+constexpr Result ResultBootReasonNotSet{ErrorModule::SPL, 107};
+constexpr Result ResultInvalidArgument{ErrorModule::SPL, 108};
} // namespace Service::SPL
diff --git a/src/core/hle/service/spl/spl_types.h b/src/core/hle/service/spl/spl_types.h
index a654e7556..91f9cc032 100644
--- a/src/core/hle/service/spl/spl_types.h
+++ b/src/core/hle/service/spl/spl_types.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp
index a81a595ea..3735e0452 100644
--- a/src/core/hle/service/ssl/ssl.cpp
+++ b/src/core/hle/service/ssl/ssl.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/service.h"
diff --git a/src/core/hle/service/ssl/ssl.h b/src/core/hle/service/ssl/ssl.h
index a3aa4b4b5..27b38a003 100644
--- a/src/core/hle/service/ssl/ssl.h
+++ b/src/core/hle/service/ssl/ssl.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/time/clock_types.h b/src/core/hle/service/time/clock_types.h
index d0cacb80c..ef070f32f 100644
--- a/src/core/hle/service/time/clock_types.h
+++ b/src/core/hle/service/time/clock_types.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -23,7 +22,7 @@ struct SteadyClockTimePoint {
s64 time_point;
Common::UUID clock_source_id;
- ResultCode GetSpanBetween(SteadyClockTimePoint other, s64& span) const {
+ Result GetSpanBetween(SteadyClockTimePoint other, s64& span) const {
span = 0;
if (clock_source_id != other.clock_source_id) {
@@ -93,9 +92,9 @@ struct ClockSnapshot {
TimeType type;
INSERT_PADDING_BYTES_NOINIT(0x2);
- static ResultCode GetCurrentTime(s64& current_time,
- const SteadyClockTimePoint& steady_clock_time_point,
- const SystemClockContext& context) {
+ static Result GetCurrentTime(s64& current_time,
+ const SteadyClockTimePoint& steady_clock_time_point,
+ const SystemClockContext& context) {
if (steady_clock_time_point.clock_source_id != context.steady_time_point.clock_source_id) {
current_time = 0;
return ERROR_TIME_MISMATCH;
diff --git a/src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h b/src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h
index 42893e3f6..0f928a5a5 100644
--- a/src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h
+++ b/src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/time/ephemeral_network_system_clock_core.h b/src/core/hle/service/time/ephemeral_network_system_clock_core.h
index d12cb5335..0a5f5aafb 100644
--- a/src/core/hle/service/time/ephemeral_network_system_clock_core.h
+++ b/src/core/hle/service/time/ephemeral_network_system_clock_core.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/time/errors.h b/src/core/hle/service/time/errors.h
index 8501a3e8c..6655d30e1 100644
--- a/src/core/hle/service/time/errors.h
+++ b/src/core/hle/service/time/errors.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,15 +7,15 @@
namespace Service::Time {
-constexpr ResultCode ERROR_PERMISSION_DENIED{ErrorModule::Time, 1};
-constexpr ResultCode ERROR_TIME_MISMATCH{ErrorModule::Time, 102};
-constexpr ResultCode ERROR_UNINITIALIZED_CLOCK{ErrorModule::Time, 103};
-constexpr ResultCode ERROR_TIME_NOT_FOUND{ErrorModule::Time, 200};
-constexpr ResultCode ERROR_OVERFLOW{ErrorModule::Time, 201};
-constexpr ResultCode ERROR_LOCATION_NAME_TOO_LONG{ErrorModule::Time, 801};
-constexpr ResultCode ERROR_OUT_OF_RANGE{ErrorModule::Time, 902};
-constexpr ResultCode ERROR_TIME_ZONE_CONVERSION_FAILED{ErrorModule::Time, 903};
-constexpr ResultCode ERROR_TIME_ZONE_NOT_FOUND{ErrorModule::Time, 989};
-constexpr ResultCode ERROR_NOT_IMPLEMENTED{ErrorModule::Time, 990};
+constexpr Result ERROR_PERMISSION_DENIED{ErrorModule::Time, 1};
+constexpr Result ERROR_TIME_MISMATCH{ErrorModule::Time, 102};
+constexpr Result ERROR_UNINITIALIZED_CLOCK{ErrorModule::Time, 103};
+constexpr Result ERROR_TIME_NOT_FOUND{ErrorModule::Time, 200};
+constexpr Result ERROR_OVERFLOW{ErrorModule::Time, 201};
+constexpr Result ERROR_LOCATION_NAME_TOO_LONG{ErrorModule::Time, 801};
+constexpr Result ERROR_OUT_OF_RANGE{ErrorModule::Time, 902};
+constexpr Result ERROR_TIME_ZONE_CONVERSION_FAILED{ErrorModule::Time, 903};
+constexpr Result ERROR_TIME_ZONE_NOT_FOUND{ErrorModule::Time, 989};
+constexpr Result ERROR_NOT_IMPLEMENTED{ErrorModule::Time, 990};
} // namespace Service::Time
diff --git a/src/core/hle/service/time/local_system_clock_context_writer.h b/src/core/hle/service/time/local_system_clock_context_writer.h
index ac6c7b4b1..1639ef2b9 100644
--- a/src/core/hle/service/time/local_system_clock_context_writer.h
+++ b/src/core/hle/service/time/local_system_clock_context_writer.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -15,7 +14,7 @@ public:
: SystemClockContextUpdateCallback{}, shared_memory{shared_memory_} {}
protected:
- ResultCode Update() override {
+ Result Update() override {
shared_memory.UpdateLocalSystemClockContext(context);
return ResultSuccess;
}
diff --git a/src/core/hle/service/time/network_system_clock_context_writer.h b/src/core/hle/service/time/network_system_clock_context_writer.h
index a54fd7fe1..655e4c06d 100644
--- a/src/core/hle/service/time/network_system_clock_context_writer.h
+++ b/src/core/hle/service/time/network_system_clock_context_writer.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -16,7 +15,7 @@ public:
: SystemClockContextUpdateCallback{}, shared_memory{shared_memory_} {}
protected:
- ResultCode Update() override {
+ Result Update() override {
shared_memory.UpdateNetworkSystemClockContext(context);
return ResultSuccess;
}
diff --git a/src/core/hle/service/time/standard_local_system_clock_core.h b/src/core/hle/service/time/standard_local_system_clock_core.h
index 6320c7af1..ae2ff1bfd 100644
--- a/src/core/hle/service/time/standard_local_system_clock_core.h
+++ b/src/core/hle/service/time/standard_local_system_clock_core.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/time/standard_network_system_clock_core.h b/src/core/hle/service/time/standard_network_system_clock_core.h
index 95923a27b..c1ec5252b 100644
--- a/src/core/hle/service/time/standard_network_system_clock_core.h
+++ b/src/core/hle/service/time/standard_network_system_clock_core.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/time/standard_steady_clock_core.cpp b/src/core/hle/service/time/standard_steady_clock_core.cpp
index a1ffdd524..3dbbb9850 100644
--- a/src/core/hle/service/time/standard_steady_clock_core.cpp
+++ b/src/core/hle/service/time/standard_steady_clock_core.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/core_timing.h"
diff --git a/src/core/hle/service/time/standard_steady_clock_core.h b/src/core/hle/service/time/standard_steady_clock_core.h
index f56f3fd95..036463b87 100644
--- a/src/core/hle/service/time/standard_steady_clock_core.h
+++ b/src/core/hle/service/time/standard_steady_clock_core.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/time/standard_user_system_clock_core.cpp b/src/core/hle/service/time/standard_user_system_clock_core.cpp
index e94220a44..b033757ed 100644
--- a/src/core/hle/service/time/standard_user_system_clock_core.cpp
+++ b/src/core/hle/service/time/standard_user_system_clock_core.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "core/core.h"
@@ -28,9 +27,9 @@ StandardUserSystemClockCore::~StandardUserSystemClockCore() {
service_context.CloseEvent(auto_correction_event);
}
-ResultCode StandardUserSystemClockCore::SetAutomaticCorrectionEnabled(Core::System& system,
- bool value) {
- if (const ResultCode result{ApplyAutomaticCorrection(system, value)}; result != ResultSuccess) {
+Result StandardUserSystemClockCore::SetAutomaticCorrectionEnabled(Core::System& system,
+ bool value) {
+ if (const Result result{ApplyAutomaticCorrection(system, value)}; result != ResultSuccess) {
return result;
}
@@ -39,27 +38,27 @@ ResultCode StandardUserSystemClockCore::SetAutomaticCorrectionEnabled(Core::Syst
return ResultSuccess;
}
-ResultCode StandardUserSystemClockCore::GetClockContext(Core::System& system,
- SystemClockContext& ctx) const {
- if (const ResultCode result{ApplyAutomaticCorrection(system, false)}; result != ResultSuccess) {
+Result StandardUserSystemClockCore::GetClockContext(Core::System& system,
+ SystemClockContext& ctx) const {
+ if (const Result result{ApplyAutomaticCorrection(system, false)}; result != ResultSuccess) {
return result;
}
return local_system_clock_core.GetClockContext(system, ctx);
}
-ResultCode StandardUserSystemClockCore::Flush(const SystemClockContext&) {
- UNREACHABLE();
+Result StandardUserSystemClockCore::Flush(const SystemClockContext&) {
+ UNIMPLEMENTED();
return ERROR_NOT_IMPLEMENTED;
}
-ResultCode StandardUserSystemClockCore::SetClockContext(const SystemClockContext&) {
- UNREACHABLE();
+Result StandardUserSystemClockCore::SetClockContext(const SystemClockContext&) {
+ UNIMPLEMENTED();
return ERROR_NOT_IMPLEMENTED;
}
-ResultCode StandardUserSystemClockCore::ApplyAutomaticCorrection(Core::System& system,
- bool value) const {
+Result StandardUserSystemClockCore::ApplyAutomaticCorrection(Core::System& system,
+ bool value) const {
if (auto_correction_enabled == value) {
return ResultSuccess;
}
@@ -69,7 +68,7 @@ ResultCode StandardUserSystemClockCore::ApplyAutomaticCorrection(Core::System& s
}
SystemClockContext ctx{};
- if (const ResultCode result{network_system_clock_core.GetClockContext(system, ctx)};
+ if (const Result result{network_system_clock_core.GetClockContext(system, ctx)};
result != ResultSuccess) {
return result;
}
diff --git a/src/core/hle/service/time/standard_user_system_clock_core.h b/src/core/hle/service/time/standard_user_system_clock_core.h
index b7cb2b045..ee6e29487 100644
--- a/src/core/hle/service/time/standard_user_system_clock_core.h
+++ b/src/core/hle/service/time/standard_user_system_clock_core.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -29,9 +28,9 @@ public:
~StandardUserSystemClockCore() override;
- ResultCode SetAutomaticCorrectionEnabled(Core::System& system, bool value);
+ Result SetAutomaticCorrectionEnabled(Core::System& system, bool value);
- ResultCode GetClockContext(Core::System& system, SystemClockContext& ctx) const override;
+ Result GetClockContext(Core::System& system, SystemClockContext& ctx) const override;
bool IsAutomaticCorrectionEnabled() const {
return auto_correction_enabled;
@@ -42,11 +41,11 @@ public:
}
protected:
- ResultCode Flush(const SystemClockContext&) override;
+ Result Flush(const SystemClockContext&) override;
- ResultCode SetClockContext(const SystemClockContext&) override;
+ Result SetClockContext(const SystemClockContext&) override;
- ResultCode ApplyAutomaticCorrection(Core::System& system, bool value) const;
+ Result ApplyAutomaticCorrection(Core::System& system, bool value) const;
const SteadyClockTimePoint& GetAutomaticCorrectionUpdatedTime() const {
return auto_correction_time;
diff --git a/src/core/hle/service/time/steady_clock_core.h b/src/core/hle/service/time/steady_clock_core.h
index 5ee2c0e0a..2867c351c 100644
--- a/src/core/hle/service/time/steady_clock_core.h
+++ b/src/core/hle/service/time/steady_clock_core.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/time/system_clock_context_update_callback.cpp b/src/core/hle/service/time/system_clock_context_update_callback.cpp
index f656fab1c..a649bed3a 100644
--- a/src/core/hle/service/time/system_clock_context_update_callback.cpp
+++ b/src/core/hle/service/time/system_clock_context_update_callback.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/service/time/errors.h"
@@ -31,8 +30,8 @@ void SystemClockContextUpdateCallback::BroadcastOperationEvent() {
}
}
-ResultCode SystemClockContextUpdateCallback::Update(const SystemClockContext& value) {
- ResultCode result{ResultSuccess};
+Result SystemClockContextUpdateCallback::Update(const SystemClockContext& value) {
+ Result result{ResultSuccess};
if (NeedUpdate(value)) {
context = value;
@@ -48,7 +47,7 @@ ResultCode SystemClockContextUpdateCallback::Update(const SystemClockContext& va
return result;
}
-ResultCode SystemClockContextUpdateCallback::Update() {
+Result SystemClockContextUpdateCallback::Update() {
return ResultSuccess;
}
diff --git a/src/core/hle/service/time/system_clock_context_update_callback.h b/src/core/hle/service/time/system_clock_context_update_callback.h
index 6936397a5..9c6caf196 100644
--- a/src/core/hle/service/time/system_clock_context_update_callback.h
+++ b/src/core/hle/service/time/system_clock_context_update_callback.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -29,10 +28,10 @@ public:
void BroadcastOperationEvent();
- ResultCode Update(const SystemClockContext& value);
+ Result Update(const SystemClockContext& value);
protected:
- virtual ResultCode Update();
+ virtual Result Update();
SystemClockContext context{};
diff --git a/src/core/hle/service/time/system_clock_core.cpp b/src/core/hle/service/time/system_clock_core.cpp
index 5c2354cdd..da078241f 100644
--- a/src/core/hle/service/time/system_clock_core.cpp
+++ b/src/core/hle/service/time/system_clock_core.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/time/steady_clock_core.h"
#include "core/hle/service/time/system_clock_context_update_callback.h"
@@ -15,13 +14,13 @@ SystemClockCore::SystemClockCore(SteadyClockCore& steady_clock_core_)
SystemClockCore::~SystemClockCore() = default;
-ResultCode SystemClockCore::GetCurrentTime(Core::System& system, s64& posix_time) const {
+Result SystemClockCore::GetCurrentTime(Core::System& system, s64& posix_time) const {
posix_time = 0;
const SteadyClockTimePoint current_time_point{steady_clock_core.GetCurrentTimePoint(system)};
SystemClockContext clock_context{};
- if (const ResultCode result{GetClockContext(system, clock_context)}; result != ResultSuccess) {
+ if (const Result result{GetClockContext(system, clock_context)}; result != ResultSuccess) {
return result;
}
@@ -34,26 +33,26 @@ ResultCode SystemClockCore::GetCurrentTime(Core::System& system, s64& posix_time
return ResultSuccess;
}
-ResultCode SystemClockCore::SetCurrentTime(Core::System& system, s64 posix_time) {
+Result SystemClockCore::SetCurrentTime(Core::System& system, s64 posix_time) {
const SteadyClockTimePoint current_time_point{steady_clock_core.GetCurrentTimePoint(system)};
const SystemClockContext clock_context{posix_time - current_time_point.time_point,
current_time_point};
- if (const ResultCode result{SetClockContext(clock_context)}; result != ResultSuccess) {
+ if (const Result result{SetClockContext(clock_context)}; result != ResultSuccess) {
return result;
}
return Flush(clock_context);
}
-ResultCode SystemClockCore::Flush(const SystemClockContext& clock_context) {
+Result SystemClockCore::Flush(const SystemClockContext& clock_context) {
if (!system_clock_context_update_callback) {
return ResultSuccess;
}
return system_clock_context_update_callback->Update(clock_context);
}
-ResultCode SystemClockCore::SetSystemClockContext(const SystemClockContext& clock_context) {
- if (const ResultCode result{SetClockContext(clock_context)}; result != ResultSuccess) {
+Result SystemClockCore::SetSystemClockContext(const SystemClockContext& clock_context) {
+ if (const Result result{SetClockContext(clock_context)}; result != ResultSuccess) {
return result;
}
return Flush(clock_context);
diff --git a/src/core/hle/service/time/system_clock_core.h b/src/core/hle/service/time/system_clock_core.h
index b9237ad28..8cb34126f 100644
--- a/src/core/hle/service/time/system_clock_core.h
+++ b/src/core/hle/service/time/system_clock_core.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -30,28 +29,28 @@ public:
return steady_clock_core;
}
- ResultCode GetCurrentTime(Core::System& system, s64& posix_time) const;
+ Result GetCurrentTime(Core::System& system, s64& posix_time) const;
- ResultCode SetCurrentTime(Core::System& system, s64 posix_time);
+ Result SetCurrentTime(Core::System& system, s64 posix_time);
- virtual ResultCode GetClockContext([[maybe_unused]] Core::System& system,
- SystemClockContext& value) const {
+ virtual Result GetClockContext([[maybe_unused]] Core::System& system,
+ SystemClockContext& value) const {
value = context;
return ResultSuccess;
}
- virtual ResultCode SetClockContext(const SystemClockContext& value) {
+ virtual Result SetClockContext(const SystemClockContext& value) {
context = value;
return ResultSuccess;
}
- virtual ResultCode Flush(const SystemClockContext& clock_context);
+ virtual Result Flush(const SystemClockContext& clock_context);
void SetUpdateCallbackInstance(std::shared_ptr<SystemClockContextUpdateCallback> callback) {
system_clock_context_update_callback = std::move(callback);
}
- ResultCode SetSystemClockContext(const SystemClockContext& context);
+ Result SetSystemClockContext(const SystemClockContext& context);
bool IsInitialized() const {
return is_initialized;
diff --git a/src/core/hle/service/time/tick_based_steady_clock_core.cpp b/src/core/hle/service/time/tick_based_steady_clock_core.cpp
index 47d4ab980..27600413e 100644
--- a/src/core/hle/service/time/tick_based_steady_clock_core.cpp
+++ b/src/core/hle/service/time/tick_based_steady_clock_core.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/core_timing.h"
diff --git a/src/core/hle/service/time/tick_based_steady_clock_core.h b/src/core/hle/service/time/tick_based_steady_clock_core.h
index 1a5a53fd7..491185dc3 100644
--- a/src/core/hle/service/time/tick_based_steady_clock_core.h
+++ b/src/core/hle/service/time/tick_based_steady_clock_core.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp
index 4d8823b5a..f77cdbb43 100644
--- a/src/core/hle/service/time/time.cpp
+++ b/src/core/hle/service/time/time.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/core.h"
@@ -44,8 +43,7 @@ private:
}
s64 posix_time{};
- if (const ResultCode result{clock_core.GetCurrentTime(system, posix_time)};
- result.IsError()) {
+ if (const Result result{clock_core.GetCurrentTime(system, posix_time)}; result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
@@ -66,7 +64,7 @@ private:
}
Clock::SystemClockContext system_clock_context{};
- if (const ResultCode result{clock_core.GetClockContext(system, system_clock_context)};
+ if (const Result result{clock_core.GetClockContext(system, system_clock_context)};
result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
@@ -117,7 +115,7 @@ private:
Clock::SteadyClockCore& clock_core;
};
-ResultCode Module::Interface::GetClockSnapshotFromSystemClockContextInternal(
+Result Module::Interface::GetClockSnapshotFromSystemClockContextInternal(
Kernel::KThread* thread, Clock::SystemClockContext user_context,
Clock::SystemClockContext network_context, Clock::TimeType type,
Clock::ClockSnapshot& clock_snapshot) {
@@ -130,7 +128,7 @@ ResultCode Module::Interface::GetClockSnapshotFromSystemClockContextInternal(
time_manager.GetStandardUserSystemClockCore().IsAutomaticCorrectionEnabled();
clock_snapshot.type = type;
- if (const ResultCode result{
+ if (const Result result{
time_manager.GetTimeZoneContentManager().GetTimeZoneManager().GetDeviceLocationName(
clock_snapshot.location_name)};
result != ResultSuccess) {
@@ -139,7 +137,7 @@ ResultCode Module::Interface::GetClockSnapshotFromSystemClockContextInternal(
clock_snapshot.user_context = user_context;
- if (const ResultCode result{Clock::ClockSnapshot::GetCurrentTime(
+ if (const Result result{Clock::ClockSnapshot::GetCurrentTime(
clock_snapshot.user_time, clock_snapshot.steady_clock_time_point,
clock_snapshot.user_context)};
result != ResultSuccess) {
@@ -147,7 +145,7 @@ ResultCode Module::Interface::GetClockSnapshotFromSystemClockContextInternal(
}
TimeZone::CalendarInfo userCalendarInfo{};
- if (const ResultCode result{
+ if (const Result result{
time_manager.GetTimeZoneContentManager().GetTimeZoneManager().ToCalendarTimeWithMyRules(
clock_snapshot.user_time, userCalendarInfo)};
result != ResultSuccess) {
@@ -166,7 +164,7 @@ ResultCode Module::Interface::GetClockSnapshotFromSystemClockContextInternal(
}
TimeZone::CalendarInfo networkCalendarInfo{};
- if (const ResultCode result{
+ if (const Result result{
time_manager.GetTimeZoneContentManager().GetTimeZoneManager().ToCalendarTimeWithMyRules(
clock_snapshot.network_time, networkCalendarInfo)};
result != ResultSuccess) {
@@ -263,7 +261,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called, type={}", type);
Clock::SystemClockContext user_context{};
- if (const ResultCode result{
+ if (const Result result{
system.GetTimeManager().GetStandardUserSystemClockCore().GetClockContext(system,
user_context)};
result.IsError()) {
@@ -273,7 +271,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
}
Clock::SystemClockContext network_context{};
- if (const ResultCode result{
+ if (const Result result{
system.GetTimeManager().GetStandardNetworkSystemClockCore().GetClockContext(
system, network_context)};
result.IsError()) {
@@ -283,7 +281,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
}
Clock::ClockSnapshot clock_snapshot{};
- if (const ResultCode result{GetClockSnapshotFromSystemClockContextInternal(
+ if (const Result result{GetClockSnapshotFromSystemClockContextInternal(
&ctx.GetThread(), user_context, network_context, type, clock_snapshot)};
result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
@@ -309,7 +307,7 @@ void Module::Interface::GetClockSnapshotFromSystemClockContext(Kernel::HLEReques
LOG_DEBUG(Service_Time, "called, type={}", type);
Clock::ClockSnapshot clock_snapshot{};
- if (const ResultCode result{GetClockSnapshotFromSystemClockContextInternal(
+ if (const Result result{GetClockSnapshotFromSystemClockContextInternal(
&ctx.GetThread(), user_context, network_context, type, clock_snapshot)};
result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
@@ -366,7 +364,7 @@ void Module::Interface::CalculateSpanBetween(Kernel::HLERequestContext& ctx) {
Clock::TimeSpanType time_span_type{};
s64 span{};
- if (const ResultCode result{snapshot_a.steady_clock_time_point.GetSpanBetween(
+ if (const Result result{snapshot_a.steady_clock_time_point.GetSpanBetween(
snapshot_b.steady_clock_time_point, span)};
result != ResultSuccess) {
if (snapshot_a.network_time && snapshot_b.network_time) {
diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h
index 30e2cd369..76a46cfc7 100644
--- a/src/core/hle/service/time/time.h
+++ b/src/core/hle/service/time/time.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -37,7 +36,7 @@ public:
void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx);
private:
- ResultCode GetClockSnapshotFromSystemClockContextInternal(
+ Result GetClockSnapshotFromSystemClockContextInternal(
Kernel::KThread* thread, Clock::SystemClockContext user_context,
Clock::SystemClockContext network_context, Clock::TimeType type,
Clock::ClockSnapshot& cloc_snapshot);
diff --git a/src/core/hle/service/time/time_interface.cpp b/src/core/hle/service/time/time_interface.cpp
index bb7b6b5c1..0c53e98ee 100644
--- a/src/core/hle/service/time/time_interface.cpp
+++ b/src/core/hle/service/time/time_interface.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/time/time_interface.h"
diff --git a/src/core/hle/service/time/time_interface.h b/src/core/hle/service/time/time_interface.h
index c41766f1a..ceeb0e5ef 100644
--- a/src/core/hle/service/time/time_interface.h
+++ b/src/core/hle/service/time/time_interface.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/time/time_manager.cpp b/src/core/hle/service/time/time_manager.cpp
index 00f1ae8cf..28667710e 100644
--- a/src/core/hle/service/time/time_manager.cpp
+++ b/src/core/hle/service/time/time_manager.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <ctime>
@@ -112,7 +111,7 @@ struct TimeManager::Impl final {
FileSys::VirtualFile& vfs_file) {
if (time_zone_content_manager.GetTimeZoneManager().SetDeviceLocationNameWithTimeZoneRule(
location_name, vfs_file) != ResultSuccess) {
- UNREACHABLE();
+ ASSERT(false);
return;
}
@@ -156,7 +155,7 @@ struct TimeManager::Impl final {
} else {
if (standard_local_system_clock_core.SetCurrentTime(system_, posix_time) !=
ResultSuccess) {
- UNREACHABLE();
+ ASSERT(false);
return;
}
}
@@ -171,7 +170,7 @@ struct TimeManager::Impl final {
if (standard_network_system_clock_core.SetSystemClockContext(clock_context) !=
ResultSuccess) {
- UNREACHABLE();
+ ASSERT(false);
return;
}
@@ -184,7 +183,7 @@ struct TimeManager::Impl final {
Clock::SteadyClockTimePoint steady_clock_time_point) {
if (standard_user_system_clock_core.SetAutomaticCorrectionEnabled(
system_, is_automatic_correction_enabled) != ResultSuccess) {
- UNREACHABLE();
+ ASSERT(false);
return;
}
@@ -204,7 +203,7 @@ struct TimeManager::Impl final {
if (GetStandardLocalSystemClockCore()
.SetCurrentTime(system_, timespan.ToSeconds())
.IsError()) {
- UNREACHABLE();
+ ASSERT(false);
return;
}
}
diff --git a/src/core/hle/service/time/time_manager.h b/src/core/hle/service/time/time_manager.h
index 2404067c0..4f046f266 100644
--- a/src/core/hle/service/time/time_manager.h
+++ b/src/core/hle/service/time/time_manager.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/time/time_sharedmemory.cpp b/src/core/hle/service/time/time_sharedmemory.cpp
index ed9f75ed6..a3aa0e77f 100644
--- a/src/core/hle/service/time/time_sharedmemory.cpp
+++ b/src/core/hle/service/time/time_sharedmemory.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/core_timing.h"
diff --git a/src/core/hle/service/time/time_sharedmemory.h b/src/core/hle/service/time/time_sharedmemory.h
index 9307ea795..561685acd 100644
--- a/src/core/hle/service/time/time_sharedmemory.h
+++ b/src/core/hle/service/time/time_sharedmemory.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp
index c634b6abd..afbfe9715 100644
--- a/src/core/hle/service/time/time_zone_content_manager.cpp
+++ b/src/core/hle/service/time/time_zone_content_manager.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <sstream>
@@ -91,10 +90,10 @@ void TimeZoneContentManager::Initialize(TimeManager& time_manager) {
}
}
-ResultCode TimeZoneContentManager::LoadTimeZoneRule(TimeZoneRule& rules,
- const std::string& location_name) const {
+Result TimeZoneContentManager::LoadTimeZoneRule(TimeZoneRule& rules,
+ const std::string& location_name) const {
FileSys::VirtualFile vfs_file;
- if (const ResultCode result{GetTimeZoneInfoFile(location_name, vfs_file)};
+ if (const Result result{GetTimeZoneInfoFile(location_name, vfs_file)};
result != ResultSuccess) {
return result;
}
@@ -107,8 +106,8 @@ bool TimeZoneContentManager::IsLocationNameValid(const std::string& location_nam
location_name_cache.end();
}
-ResultCode TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& location_name,
- FileSys::VirtualFile& vfs_file) const {
+Result TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& location_name,
+ FileSys::VirtualFile& vfs_file) const {
if (!IsLocationNameValid(location_name)) {
return ERROR_TIME_NOT_FOUND;
}
diff --git a/src/core/hle/service/time/time_zone_content_manager.h b/src/core/hle/service/time/time_zone_content_manager.h
index cfa601084..3d94b6428 100644
--- a/src/core/hle/service/time/time_zone_content_manager.h
+++ b/src/core/hle/service/time/time_zone_content_manager.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -33,12 +32,12 @@ public:
return time_zone_manager;
}
- ResultCode LoadTimeZoneRule(TimeZoneRule& rules, const std::string& location_name) const;
+ Result LoadTimeZoneRule(TimeZoneRule& rules, const std::string& location_name) const;
private:
bool IsLocationNameValid(const std::string& location_name) const;
- ResultCode GetTimeZoneInfoFile(const std::string& location_name,
- FileSys::VirtualFile& vfs_file) const;
+ Result GetTimeZoneInfoFile(const std::string& location_name,
+ FileSys::VirtualFile& vfs_file) const;
Core::System& system;
TimeZoneManager time_zone_manager;
diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp
index 2989cee5e..2aa675df9 100644
--- a/src/core/hle/service/time/time_zone_manager.cpp
+++ b/src/core/hle/service/time/time_zone_manager.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <climits>
@@ -111,10 +110,9 @@ static constexpr s64 GetLeapDaysFromYear(s64 year) {
}
}
-static constexpr int GetMonthLength(bool is_leap_year, int month) {
- constexpr std::array<int, 12> month_lengths{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
- constexpr std::array<int, 12> month_lengths_leap{31, 29, 31, 30, 31, 30,
- 31, 31, 30, 31, 30, 31};
+static constexpr s8 GetMonthLength(bool is_leap_year, int month) {
+ constexpr std::array<s8, 12> month_lengths{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+ constexpr std::array<s8, 12> month_lengths_leap{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return is_leap_year ? month_lengths_leap[month] : month_lengths[month];
}
@@ -281,7 +279,7 @@ static constexpr int TransitionTime(int year, Rule rule, int offset) {
break;
}
default:
- UNREACHABLE();
+ ASSERT(false);
}
return value + rule.transition_time + offset;
}
@@ -668,8 +666,8 @@ static bool ParseTimeZoneBinary(TimeZoneRule& time_zone_rule, FileSys::VirtualFi
return true;
}
-static ResultCode CreateCalendarTime(s64 time, int gmt_offset, CalendarTimeInternal& calendar_time,
- CalendarAdditionalInfo& calendar_additional_info) {
+static Result CreateCalendarTime(s64 time, int gmt_offset, CalendarTimeInternal& calendar_time,
+ CalendarAdditionalInfo& calendar_additional_info) {
s64 year{epoch_year};
s64 time_days{time / seconds_per_day};
s64 remaining_seconds{time % seconds_per_day};
@@ -743,9 +741,9 @@ static ResultCode CreateCalendarTime(s64 time, int gmt_offset, CalendarTimeInter
return ResultSuccess;
}
-static ResultCode ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time,
- CalendarTimeInternal& calendar_time,
- CalendarAdditionalInfo& calendar_additional_info) {
+static Result ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time,
+ CalendarTimeInternal& calendar_time,
+ CalendarAdditionalInfo& calendar_additional_info) {
if ((rules.go_ahead && time < rules.ats[0]) ||
(rules.go_back && time > rules.ats[rules.time_count - 1])) {
s64 seconds{};
@@ -768,7 +766,7 @@ static ResultCode ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time,
if (new_time < rules.ats[0] && new_time > rules.ats[rules.time_count - 1]) {
return ERROR_TIME_NOT_FOUND;
}
- if (const ResultCode result{
+ if (const Result result{
ToCalendarTimeInternal(rules, new_time, calendar_time, calendar_additional_info)};
result != ResultSuccess) {
return result;
@@ -799,8 +797,8 @@ static ResultCode ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time,
tti_index = rules.types[low - 1];
}
- if (const ResultCode result{CreateCalendarTime(time, rules.ttis[tti_index].gmt_offset,
- calendar_time, calendar_additional_info)};
+ if (const Result result{CreateCalendarTime(time, rules.ttis[tti_index].gmt_offset,
+ calendar_time, calendar_additional_info)};
result != ResultSuccess) {
return result;
}
@@ -813,9 +811,9 @@ static ResultCode ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time,
return ResultSuccess;
}
-static ResultCode ToCalendarTimeImpl(const TimeZoneRule& rules, s64 time, CalendarInfo& calendar) {
+static Result ToCalendarTimeImpl(const TimeZoneRule& rules, s64 time, CalendarInfo& calendar) {
CalendarTimeInternal calendar_time{};
- const ResultCode result{
+ const Result result{
ToCalendarTimeInternal(rules, time, calendar_time, calendar.additional_info)};
calendar.time.year = static_cast<s16>(calendar_time.year);
@@ -832,13 +830,13 @@ static ResultCode ToCalendarTimeImpl(const TimeZoneRule& rules, s64 time, Calend
TimeZoneManager::TimeZoneManager() = default;
TimeZoneManager::~TimeZoneManager() = default;
-ResultCode TimeZoneManager::ToCalendarTime(const TimeZoneRule& rules, s64 time,
- CalendarInfo& calendar) const {
+Result TimeZoneManager::ToCalendarTime(const TimeZoneRule& rules, s64 time,
+ CalendarInfo& calendar) const {
return ToCalendarTimeImpl(rules, time, calendar);
}
-ResultCode TimeZoneManager::SetDeviceLocationNameWithTimeZoneRule(const std::string& location_name,
- FileSys::VirtualFile& vfs_file) {
+Result TimeZoneManager::SetDeviceLocationNameWithTimeZoneRule(const std::string& location_name,
+ FileSys::VirtualFile& vfs_file) {
TimeZoneRule rule{};
if (ParseTimeZoneBinary(rule, vfs_file)) {
device_location_name = location_name;
@@ -848,12 +846,12 @@ ResultCode TimeZoneManager::SetDeviceLocationNameWithTimeZoneRule(const std::str
return ERROR_TIME_ZONE_CONVERSION_FAILED;
}
-ResultCode TimeZoneManager::SetUpdatedTime(const Clock::SteadyClockTimePoint& value) {
+Result TimeZoneManager::SetUpdatedTime(const Clock::SteadyClockTimePoint& value) {
time_zone_update_time_point = value;
return ResultSuccess;
}
-ResultCode TimeZoneManager::ToCalendarTimeWithMyRules(s64 time, CalendarInfo& calendar) const {
+Result TimeZoneManager::ToCalendarTimeWithMyRules(s64 time, CalendarInfo& calendar) const {
if (is_initialized) {
return ToCalendarTime(time_zone_rule, time, calendar);
} else {
@@ -861,16 +859,16 @@ ResultCode TimeZoneManager::ToCalendarTimeWithMyRules(s64 time, CalendarInfo& ca
}
}
-ResultCode TimeZoneManager::ParseTimeZoneRuleBinary(TimeZoneRule& rules,
- FileSys::VirtualFile& vfs_file) const {
+Result TimeZoneManager::ParseTimeZoneRuleBinary(TimeZoneRule& rules,
+ FileSys::VirtualFile& vfs_file) const {
if (!ParseTimeZoneBinary(rules, vfs_file)) {
return ERROR_TIME_ZONE_CONVERSION_FAILED;
}
return ResultSuccess;
}
-ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules,
- const CalendarTime& calendar_time, s64& posix_time) const {
+Result TimeZoneManager::ToPosixTime(const TimeZoneRule& rules, const CalendarTime& calendar_time,
+ s64& posix_time) const {
posix_time = 0;
CalendarTimeInternal internal_time{
@@ -1022,8 +1020,8 @@ ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules,
return ResultSuccess;
}
-ResultCode TimeZoneManager::ToPosixTimeWithMyRule(const CalendarTime& calendar_time,
- s64& posix_time) const {
+Result TimeZoneManager::ToPosixTimeWithMyRule(const CalendarTime& calendar_time,
+ s64& posix_time) const {
if (is_initialized) {
return ToPosixTime(time_zone_rule, calendar_time, posix_time);
}
@@ -1031,7 +1029,7 @@ ResultCode TimeZoneManager::ToPosixTimeWithMyRule(const CalendarTime& calendar_t
return ERROR_UNINITIALIZED_CLOCK;
}
-ResultCode TimeZoneManager::GetDeviceLocationName(LocationName& value) const {
+Result TimeZoneManager::GetDeviceLocationName(LocationName& value) const {
if (!is_initialized) {
return ERROR_UNINITIALIZED_CLOCK;
}
diff --git a/src/core/hle/service/time/time_zone_manager.h b/src/core/hle/service/time/time_zone_manager.h
index aaab0a1e0..5ebd4035e 100644
--- a/src/core/hle/service/time/time_zone_manager.h
+++ b/src/core/hle/service/time/time_zone_manager.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -30,16 +29,16 @@ public:
is_initialized = true;
}
- ResultCode SetDeviceLocationNameWithTimeZoneRule(const std::string& location_name,
- FileSys::VirtualFile& vfs_file);
- ResultCode SetUpdatedTime(const Clock::SteadyClockTimePoint& value);
- ResultCode GetDeviceLocationName(TimeZone::LocationName& value) const;
- ResultCode ToCalendarTime(const TimeZoneRule& rules, s64 time, CalendarInfo& calendar) const;
- ResultCode ToCalendarTimeWithMyRules(s64 time, CalendarInfo& calendar) const;
- ResultCode ParseTimeZoneRuleBinary(TimeZoneRule& rules, FileSys::VirtualFile& vfs_file) const;
- ResultCode ToPosixTime(const TimeZoneRule& rules, const CalendarTime& calendar_time,
- s64& posix_time) const;
- ResultCode ToPosixTimeWithMyRule(const CalendarTime& calendar_time, s64& posix_time) const;
+ Result SetDeviceLocationNameWithTimeZoneRule(const std::string& location_name,
+ FileSys::VirtualFile& vfs_file);
+ Result SetUpdatedTime(const Clock::SteadyClockTimePoint& value);
+ Result GetDeviceLocationName(TimeZone::LocationName& value) const;
+ Result ToCalendarTime(const TimeZoneRule& rules, s64 time, CalendarInfo& calendar) const;
+ Result ToCalendarTimeWithMyRules(s64 time, CalendarInfo& calendar) const;
+ Result ParseTimeZoneRuleBinary(TimeZoneRule& rules, FileSys::VirtualFile& vfs_file) const;
+ Result ToPosixTime(const TimeZoneRule& rules, const CalendarTime& calendar_time,
+ s64& posix_time) const;
+ Result ToPosixTimeWithMyRule(const CalendarTime& calendar_time, s64& posix_time) const;
private:
bool is_initialized{};
diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp
index 3871e7316..961040bfc 100644
--- a/src/core/hle/service/time/time_zone_service.cpp
+++ b/src/core/hle/service/time/time_zone_service.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
@@ -33,7 +32,7 @@ void ITimeZoneService::GetDeviceLocationName(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
TimeZone::LocationName location_name{};
- if (const ResultCode result{
+ if (const Result result{
time_zone_content_manager.GetTimeZoneManager().GetDeviceLocationName(location_name)};
result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
@@ -62,7 +61,7 @@ void ITimeZoneService::LoadTimeZoneRule(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called, location_name={}", location_name);
TimeZone::TimeZoneRule time_zone_rule{};
- if (const ResultCode result{
+ if (const Result result{
time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)};
result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
@@ -89,7 +88,7 @@ void ITimeZoneService::ToCalendarTime(Kernel::HLERequestContext& ctx) {
std::memcpy(&time_zone_rule, buffer.data(), buffer.size());
TimeZone::CalendarInfo calendar_info{};
- if (const ResultCode result{time_zone_content_manager.GetTimeZoneManager().ToCalendarTime(
+ if (const Result result{time_zone_content_manager.GetTimeZoneManager().ToCalendarTime(
time_zone_rule, posix_time, calendar_info)};
result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
@@ -109,7 +108,7 @@ void ITimeZoneService::ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx)
LOG_DEBUG(Service_Time, "called, posix_time=0x{:016X}", posix_time);
TimeZone::CalendarInfo calendar_info{};
- if (const ResultCode result{
+ if (const Result result{
time_zone_content_manager.GetTimeZoneManager().ToCalendarTimeWithMyRules(
posix_time, calendar_info)};
result != ResultSuccess) {
@@ -132,7 +131,7 @@ void ITimeZoneService::ToPosixTime(Kernel::HLERequestContext& ctx) {
std::memcpy(&time_zone_rule, ctx.ReadBuffer().data(), sizeof(TimeZone::TimeZoneRule));
s64 posix_time{};
- if (const ResultCode result{time_zone_content_manager.GetTimeZoneManager().ToPosixTime(
+ if (const Result result{time_zone_content_manager.GetTimeZoneManager().ToPosixTime(
time_zone_rule, calendar_time, posix_time)};
result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
@@ -155,9 +154,8 @@ void ITimeZoneService::ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) {
const auto calendar_time{rp.PopRaw<TimeZone::CalendarTime>()};
s64 posix_time{};
- if (const ResultCode result{
- time_zone_content_manager.GetTimeZoneManager().ToPosixTimeWithMyRule(calendar_time,
- posix_time)};
+ if (const Result result{time_zone_content_manager.GetTimeZoneManager().ToPosixTimeWithMyRule(
+ calendar_time, posix_time)};
result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
diff --git a/src/core/hle/service/time/time_zone_service.h b/src/core/hle/service/time/time_zone_service.h
index 2c9b97603..f151f4b56 100644
--- a/src/core/hle/service/time/time_zone_service.h
+++ b/src/core/hle/service/time/time_zone_service.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/time/time_zone_types.h b/src/core/hle/service/time/time_zone_types.h
index d39103253..eb4fb52d1 100644
--- a/src/core/hle/service/time/time_zone_types.h
+++ b/src/core/hle/service/time/time_zone_types.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/usb/usb.cpp b/src/core/hle/service/usb/usb.cpp
index 0747c33cd..ac46a406c 100644
--- a/src/core/hle/service/usb/usb.cpp
+++ b/src/core/hle/service/usb/usb.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
diff --git a/src/core/hle/service/usb/usb.h b/src/core/hle/service/usb/usb.h
index fc366df34..b41b9684c 100644
--- a/src/core/hle/service/usb/usb.h
+++ b/src/core/hle/service/usb/usb.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
index b7705c02a..288aafaaf 100644
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ b/src/core/hle/service/vi/display/vi_display.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <utility>
@@ -13,14 +12,38 @@
#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/nvdrv/core/container.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"
+#include "core/hle/service/vi/vi_results.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,
+ Service::Nvidia::NvCore::NvMap& nvmap) {
+ auto buffer_queue_core = std::make_shared<android::BufferQueueCore>();
+ return {
+ buffer_queue_core,
+ std::make_unique<android::BufferQueueProducer>(service_context, buffer_queue_core, nvmap),
+ std::make_unique<android::BufferQueueConsumer>(buffer_queue_core, nvmap)};
+}
+
+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));
}
@@ -36,29 +59,48 @@ const Layer& Display::GetLayer(std::size_t index) const {
return *layers.at(index);
}
-Kernel::KReadableEvent& Display::GetVSyncEvent() {
- return vsync_event->GetReadableEvent();
+ResultVal<Kernel::KReadableEvent*> Display::GetVSyncEvent() {
+ if (got_vsync_event) {
+ return ResultPermissionDenied;
+ }
+
+ got_vsync_event = true;
+
+ return GetVSyncEventUnchecked();
+}
+
+Kernel::KReadableEvent* Display::GetVSyncEventUnchecked() {
+ return &vsync_event->GetReadableEvent();
}
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,
+ Service::Nvidia::NvCore::Container& nv_core) {
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, nv_core.GetNvMapFile());
+
+ 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 +112,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..33d5f398c 100644
--- a/src/core/hle/service/vi/display/vi_display.h
+++ b/src/core/hle/service/vi/display/vi_display.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,17 +9,28 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
+#include "core/hle/result.h"
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::Nvidia::NvCore {
+class Container;
+class NvMap;
+} // namespace Service::Nvidia::NvCore
namespace Service::VI {
@@ -35,12 +45,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,18 +75,30 @@ public:
/// Gets a layer for this display based off an index.
const Layer& GetLayer(std::size_t index) const;
- /// Gets the readable vsync event.
- Kernel::KReadableEvent& GetVSyncEvent();
+ std::size_t GetNumLayers() const {
+ return layers.size();
+ }
+
+ /**
+ * Gets the internal vsync event.
+ *
+ * @returns The internal Vsync event if it has not yet been retrieved,
+ * VI::ResultPermissionDenied otherwise.
+ */
+ [[nodiscard]] ResultVal<Kernel::KReadableEvent*> GetVSyncEvent();
+
+ /// Gets the internal vsync event.
+ Kernel::KReadableEvent* GetVSyncEventUnchecked();
/// Signals the internal vsync event.
void SignalVSyncEvent();
/// 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, Service::Nvidia::NvCore::Container& core);
/// Closes and removes a layer from this display with the given ID.
///
@@ -104,10 +127,12 @@ 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{};
+ bool got_vsync_event{false};
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/layer/vi_layer.cpp b/src/core/hle/service/vi/layer/vi_layer.cpp
index 9bc382587..9ae2e0e44 100644
--- a/src/core/hle/service/vi/layer/vi_layer.cpp
+++ b/src/core/hle/service/vi/layer/vi_layer.cpp
@@ -1,12 +1,15 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/vi/layer/vi_layer.h"
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..8cf1b5275 100644
--- a/src/core/hle/service/vi/layer/vi_layer.h
+++ b/src/core/hle/service/vi/layer/vi_layer.h
@@ -1,14 +1,17 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#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 +20,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 +36,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..9c917cacf 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -22,21 +21,20 @@
#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"
+#include "core/hle/service/vi/vi_results.h"
#include "core/hle/service/vi/vi_s.h"
#include "core/hle/service/vi/vi_u.h"
namespace Service::VI {
-constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1};
-constexpr ResultCode ERR_PERMISSION_DENIED{ErrorModule::VI, 5};
-constexpr ResultCode ERR_UNSUPPORTED{ErrorModule::VI, 6};
-constexpr ResultCode ERR_NOT_FOUND{ErrorModule::VI, 7};
-
struct DisplayInfo {
/// The name of this particular display.
char display_name[0x40]{"Default"};
@@ -57,447 +55,26 @@ struct DisplayInfo {
};
static_assert(sizeof(DisplayInfo) == 0x60, "DisplayInfo has wrong size");
-class Parcel {
-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 {
+class NativeWindow final {
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);
- }
+ constexpr explicit NativeWindow(u32 id_) : id{id_} {}
+ constexpr explicit NativeWindow(const NativeWindow& other) = default;
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);
- }
-
-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 +85,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 +120,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> {
@@ -899,7 +345,7 @@ private:
if (!layer_id) {
LOG_ERROR(Service_VI, "Layer not found! display=0x{:016X}", display);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(ResultNotFound);
return;
}
@@ -937,7 +383,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 +440,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 +464,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) {
@@ -1016,7 +495,7 @@ private:
if (!display_id) {
LOG_ERROR(Service_VI, "Display not found! display_name={}", name);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(ResultNotFound);
return;
}
@@ -1072,14 +551,14 @@ private:
if (scaling_mode > NintendoScaleMode::PreserveAspectRatio) {
LOG_ERROR(Service_VI, "Invalid scaling mode provided.");
- rb.Push(ERR_OPERATION_FAILED);
+ rb.Push(ResultOperationFailed);
return;
}
if (scaling_mode != NintendoScaleMode::ScaleToWindow &&
scaling_mode != NintendoScaleMode::PreserveAspectRatio) {
LOG_ERROR(Service_VI, "Unsupported scaling mode supplied.");
- rb.Push(ERR_UNSUPPORTED);
+ rb.Push(ResultNotSupported);
return;
}
@@ -1089,7 +568,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);
@@ -1112,7 +591,7 @@ private:
if (!display_id) {
LOG_ERROR(Service_VI, "Layer not found! layer_id={}", layer_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(ResultNotFound);
return;
}
@@ -1120,12 +599,12 @@ private:
if (!buffer_queue_id) {
LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", *display_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(ResultNotFound);
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);
@@ -1158,7 +637,7 @@ private:
if (!layer_id) {
LOG_ERROR(Service_VI, "Layer not found! display_id={}", display_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(ResultNotFound);
return;
}
@@ -1166,12 +645,12 @@ private:
if (!buffer_queue_id) {
LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", display_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(ResultNotFound);
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);
@@ -1193,19 +672,23 @@ private:
IPC::RequestParser rp{ctx};
const u64 display_id = rp.Pop<u64>();
- LOG_WARNING(Service_VI, "(STUBBED) called. display_id=0x{:016X}", display_id);
+ LOG_DEBUG(Service_VI, "called. display_id={}", display_id);
const auto vsync_event = nv_flinger.FindVsyncEvent(display_id);
- if (!vsync_event) {
- LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
+ if (vsync_event.Failed()) {
+ const auto result = vsync_event.Code();
+ if (result == ResultNotFound) {
+ LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
+ }
+
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(result);
return;
}
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
- rb.PushCopyObjects(vsync_event);
+ rb.PushCopyObjects(*vsync_event);
}
void ConvertScalingMode(Kernel::HLERequestContext& ctx) {
@@ -1282,44 +765,14 @@ private:
return ConvertedScaleMode::PreserveAspectRatio;
default:
LOG_ERROR(Service_VI, "Invalid scaling mode specified, mode={}", mode);
- return ERR_OPERATION_FAILED;
+ return ResultOperationFailed;
}
}
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,27 +786,33 @@ 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>();
if (!IsValidServiceAccess(permission, policy)) {
LOG_ERROR(Service_VI, "Permission denied for policy {}", policy);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_PERMISSION_DENIED);
+ rb.Push(ResultPermissionDenied);
return;
}
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..fc2d717e7 100644
--- a/src/core/hle/service/vi/vi.h
+++ b/src/core/hle/service/vi/vi.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -15,8 +14,9 @@ class HLERequestContext;
}
namespace Service::NVFlinger {
+class HosBinderDriverServer;
class NVFlinger;
-}
+} // namespace Service::NVFlinger
namespace Service::SM {
class ServiceManager;
@@ -47,11 +47,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..1ab7fe4ab 100644
--- a/src/core/hle/service/vi/vi_m.cpp
+++ b/src/core/hle/service/vi/vi_m.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/hle/service/vi/vi.h"
@@ -8,8 +7,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 +23,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..3bf76d439 100644
--- a/src/core/hle/service/vi/vi_m.h
+++ b/src/core/hle/service/vi/vi_m.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -15,20 +14,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_results.h b/src/core/hle/service/vi/vi_results.h
new file mode 100644
index 000000000..a46c247d2
--- /dev/null
+++ b/src/core/hle/service/vi/vi_results.h
@@ -0,0 +1,13 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/result.h"
+
+namespace Service::VI {
+
+constexpr Result ResultOperationFailed{ErrorModule::VI, 1};
+constexpr Result ResultPermissionDenied{ErrorModule::VI, 5};
+constexpr Result ResultNotSupported{ErrorModule::VI, 6};
+constexpr Result ResultNotFound{ErrorModule::VI, 7};
+
+} // 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..fd799dac1 100644
--- a/src/core/hle/service/vi/vi_s.cpp
+++ b/src/core/hle/service/vi/vi_s.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/hle/service/vi/vi.h"
@@ -8,8 +7,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 +23,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..97503ac7f 100644
--- a/src/core/hle/service/vi/vi_s.h
+++ b/src/core/hle/service/vi/vi_s.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -15,20 +14,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..6cc54bd13 100644
--- a/src/core/hle/service/vi/vi_u.cpp
+++ b/src/core/hle/service/vi/vi_u.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/hle/service/vi/vi.h"
@@ -8,8 +7,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 +23,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..797941bd7 100644
--- a/src/core/hle/service/vi/vi_u.h
+++ b/src/core/hle/service/vi/vi_u.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -15,20 +14,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/hle/service/wlan/wlan.cpp b/src/core/hle/service/wlan/wlan.cpp
index f10b8c853..226e3034c 100644
--- a/src/core/hle/service/wlan/wlan.cpp
+++ b/src/core/hle/service/wlan/wlan.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
diff --git a/src/core/hle/service/wlan/wlan.h b/src/core/hle/service/wlan/wlan.h
index 3899eedbb..535c3bf0d 100644
--- a/src/core/hle/service/wlan/wlan.h
+++ b/src/core/hle/service/wlan/wlan.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp
new file mode 100644
index 000000000..447fbffaa
--- /dev/null
+++ b/src/core/internal_network/network.cpp
@@ -0,0 +1,639 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <cstring>
+#include <limits>
+#include <utility>
+#include <vector>
+
+#include "common/error.h"
+
+#ifdef _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#elif YUZU_UNIX
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#else
+#error "Unimplemented platform"
+#endif
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "core/internal_network/network.h"
+#include "core/internal_network/network_interface.h"
+#include "core/internal_network/sockets.h"
+#include "network/network.h"
+
+namespace Network {
+
+namespace {
+
+#ifdef _WIN32
+
+using socklen_t = int;
+
+void Initialize() {
+ WSADATA wsa_data;
+ (void)WSAStartup(MAKEWORD(2, 2), &wsa_data);
+}
+
+void Finalize() {
+ WSACleanup();
+}
+
+sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
+ sockaddr_in result;
+
+#if YUZU_UNIX
+ result.sin_len = sizeof(result);
+#endif
+
+ switch (static_cast<Domain>(input.family)) {
+ case Domain::INET:
+ result.sin_family = AF_INET;
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.family);
+ result.sin_family = AF_INET;
+ break;
+ }
+
+ result.sin_port = htons(input.portno);
+
+ auto& ip = result.sin_addr.S_un.S_un_b;
+ ip.s_b1 = input.ip[0];
+ ip.s_b2 = input.ip[1];
+ ip.s_b3 = input.ip[2];
+ ip.s_b4 = input.ip[3];
+
+ sockaddr addr;
+ std::memcpy(&addr, &result, sizeof(addr));
+ return addr;
+}
+
+LINGER MakeLinger(bool enable, u32 linger_value) {
+ ASSERT(linger_value <= std::numeric_limits<u_short>::max());
+
+ LINGER value;
+ value.l_onoff = enable ? 1 : 0;
+ value.l_linger = static_cast<u_short>(linger_value);
+ return value;
+}
+
+bool EnableNonBlock(SOCKET fd, bool enable) {
+ u_long value = enable ? 1 : 0;
+ return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR;
+}
+
+Errno TranslateNativeError(int e) {
+ switch (e) {
+ case WSAEBADF:
+ return Errno::BADF;
+ case WSAEINVAL:
+ return Errno::INVAL;
+ case WSAEMFILE:
+ return Errno::MFILE;
+ case WSAENOTCONN:
+ return Errno::NOTCONN;
+ case WSAEWOULDBLOCK:
+ return Errno::AGAIN;
+ case WSAECONNREFUSED:
+ return Errno::CONNREFUSED;
+ case WSAEHOSTUNREACH:
+ return Errno::HOSTUNREACH;
+ case WSAENETDOWN:
+ return Errno::NETDOWN;
+ case WSAENETUNREACH:
+ return Errno::NETUNREACH;
+ case WSAEMSGSIZE:
+ return Errno::MSGSIZE;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
+ return Errno::OTHER;
+ }
+}
+
+#elif YUZU_UNIX // ^ _WIN32 v YUZU_UNIX
+
+using SOCKET = int;
+using WSAPOLLFD = pollfd;
+using ULONG = u64;
+
+constexpr SOCKET SOCKET_ERROR = -1;
+
+constexpr int SD_RECEIVE = SHUT_RD;
+constexpr int SD_SEND = SHUT_WR;
+constexpr int SD_BOTH = SHUT_RDWR;
+
+void Initialize() {}
+
+void Finalize() {}
+
+sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
+ sockaddr_in result;
+
+ switch (static_cast<Domain>(input.family)) {
+ case Domain::INET:
+ result.sin_family = AF_INET;
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.family);
+ result.sin_family = AF_INET;
+ break;
+ }
+
+ result.sin_port = htons(input.portno);
+
+ result.sin_addr.s_addr = input.ip[0] | input.ip[1] << 8 | input.ip[2] << 16 | input.ip[3] << 24;
+
+ sockaddr addr;
+ std::memcpy(&addr, &result, sizeof(addr));
+ return addr;
+}
+
+int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) {
+ return poll(fds, static_cast<nfds_t>(nfds), timeout);
+}
+
+int closesocket(SOCKET fd) {
+ return close(fd);
+}
+
+linger MakeLinger(bool enable, u32 linger_value) {
+ linger value;
+ value.l_onoff = enable ? 1 : 0;
+ value.l_linger = linger_value;
+ return value;
+}
+
+bool EnableNonBlock(int fd, bool enable) {
+ int flags = fcntl(fd, F_GETFL);
+ if (flags == -1) {
+ return false;
+ }
+ if (enable) {
+ flags |= O_NONBLOCK;
+ } else {
+ flags &= ~O_NONBLOCK;
+ }
+ return fcntl(fd, F_SETFL, flags) == 0;
+}
+
+Errno TranslateNativeError(int e) {
+ switch (e) {
+ case EBADF:
+ return Errno::BADF;
+ case EINVAL:
+ return Errno::INVAL;
+ case EMFILE:
+ return Errno::MFILE;
+ case ENOTCONN:
+ return Errno::NOTCONN;
+ case EAGAIN:
+ return Errno::AGAIN;
+ case ECONNREFUSED:
+ return Errno::CONNREFUSED;
+ case EHOSTUNREACH:
+ return Errno::HOSTUNREACH;
+ case ENETDOWN:
+ return Errno::NETDOWN;
+ case ENETUNREACH:
+ return Errno::NETUNREACH;
+ case EMSGSIZE:
+ return Errno::MSGSIZE;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
+ return Errno::OTHER;
+ }
+}
+
+#endif
+
+Errno GetAndLogLastError() {
+#ifdef _WIN32
+ int e = WSAGetLastError();
+#else
+ int e = errno;
+#endif
+ const Errno err = TranslateNativeError(e);
+ if (err == Errno::AGAIN) {
+ return err;
+ }
+ LOG_ERROR(Network, "Socket operation error: {}", Common::NativeErrorToString(e));
+ return err;
+}
+
+int TranslateDomain(Domain domain) {
+ switch (domain) {
+ case Domain::INET:
+ return AF_INET;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented domain={}", domain);
+ return 0;
+ }
+}
+
+int TranslateType(Type type) {
+ switch (type) {
+ case Type::STREAM:
+ return SOCK_STREAM;
+ case Type::DGRAM:
+ return SOCK_DGRAM;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented type={}", type);
+ return 0;
+ }
+}
+
+int TranslateProtocol(Protocol protocol) {
+ switch (protocol) {
+ case Protocol::TCP:
+ return IPPROTO_TCP;
+ case Protocol::UDP:
+ return IPPROTO_UDP;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol);
+ return 0;
+ }
+}
+
+SockAddrIn TranslateToSockAddrIn(sockaddr input_) {
+ sockaddr_in input;
+ std::memcpy(&input, &input_, sizeof(input));
+
+ SockAddrIn result;
+
+ switch (input.sin_family) {
+ case AF_INET:
+ result.family = Domain::INET;
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.sin_family);
+ result.family = Domain::INET;
+ break;
+ }
+
+ result.portno = ntohs(input.sin_port);
+
+ result.ip = TranslateIPv4(input.sin_addr);
+
+ return result;
+}
+
+short TranslatePollEvents(PollEvents events) {
+ short result = 0;
+
+ if (True(events & PollEvents::In)) {
+ events &= ~PollEvents::In;
+ result |= POLLIN;
+ }
+ if (True(events & PollEvents::Pri)) {
+ events &= ~PollEvents::Pri;
+#ifdef _WIN32
+ LOG_WARNING(Service, "Winsock doesn't support POLLPRI");
+#else
+ result |= POLLPRI;
+#endif
+ }
+ if (True(events & PollEvents::Out)) {
+ events &= ~PollEvents::Out;
+ result |= POLLOUT;
+ }
+
+ UNIMPLEMENTED_IF_MSG((u16)events != 0, "Unhandled guest events=0x{:x}", (u16)events);
+
+ return result;
+}
+
+PollEvents TranslatePollRevents(short revents) {
+ PollEvents result{};
+ const auto translate = [&result, &revents](short host, PollEvents guest) {
+ if ((revents & host) != 0) {
+ revents &= static_cast<short>(~host);
+ result |= guest;
+ }
+ };
+
+ translate(POLLIN, PollEvents::In);
+ translate(POLLPRI, PollEvents::Pri);
+ translate(POLLOUT, PollEvents::Out);
+ translate(POLLERR, PollEvents::Err);
+ translate(POLLHUP, PollEvents::Hup);
+
+ UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents=0x{:x}", revents);
+
+ return result;
+}
+
+} // Anonymous namespace
+
+NetworkInstance::NetworkInstance() {
+ Initialize();
+}
+
+NetworkInstance::~NetworkInstance() {
+ Finalize();
+}
+
+std::optional<IPv4Address> GetHostIPv4Address() {
+ const auto network_interface = Network::GetSelectedNetworkInterface();
+ if (!network_interface.has_value()) {
+ LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface");
+ return {};
+ }
+
+ std::array<char, 16> ip_addr = {};
+ ASSERT(inet_ntop(AF_INET, &network_interface->ip_address, ip_addr.data(), sizeof(ip_addr)) !=
+ nullptr);
+ return TranslateIPv4(network_interface->ip_address);
+}
+
+std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
+ const size_t num = pollfds.size();
+
+ std::vector<WSAPOLLFD> host_pollfds(pollfds.size());
+ std::transform(pollfds.begin(), pollfds.end(), host_pollfds.begin(), [](PollFD fd) {
+ WSAPOLLFD result;
+ result.fd = fd.socket->GetFD();
+ result.events = TranslatePollEvents(fd.events);
+ result.revents = 0;
+ return result;
+ });
+
+ const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout);
+ if (result == 0) {
+ ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(),
+ [](WSAPOLLFD fd) { return fd.revents == 0; }));
+ return {0, Errno::SUCCESS};
+ }
+
+ for (size_t i = 0; i < num; ++i) {
+ pollfds[i].revents = TranslatePollRevents(host_pollfds[i].revents);
+ }
+
+ if (result > 0) {
+ return {result, Errno::SUCCESS};
+ }
+
+ ASSERT(result == SOCKET_ERROR);
+
+ return {-1, GetAndLogLastError()};
+}
+
+Socket::~Socket() {
+ if (fd == INVALID_SOCKET) {
+ return;
+ }
+ (void)closesocket(fd);
+ fd = INVALID_SOCKET;
+}
+
+Socket::Socket(Socket&& rhs) noexcept {
+ fd = std::exchange(rhs.fd, INVALID_SOCKET);
+}
+
+template <typename T>
+Errno Socket::SetSockOpt(SOCKET fd_, int option, T value) {
+ const int result =
+ setsockopt(fd_, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value));
+ if (result != SOCKET_ERROR) {
+ return Errno::SUCCESS;
+ }
+ return GetAndLogLastError();
+}
+
+Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
+ fd = socket(TranslateDomain(domain), TranslateType(type), TranslateProtocol(protocol));
+ if (fd != INVALID_SOCKET) {
+ return Errno::SUCCESS;
+ }
+
+ return GetAndLogLastError();
+}
+
+std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() {
+ sockaddr addr;
+ socklen_t addrlen = sizeof(addr);
+ const SOCKET new_socket = accept(fd, &addr, &addrlen);
+
+ if (new_socket == INVALID_SOCKET) {
+ return {AcceptResult{}, GetAndLogLastError()};
+ }
+
+ ASSERT(addrlen == sizeof(sockaddr_in));
+
+ AcceptResult result{
+ .socket = std::make_unique<Socket>(new_socket),
+ .sockaddr_in = TranslateToSockAddrIn(addr),
+ };
+
+ return {std::move(result), Errno::SUCCESS};
+}
+
+Errno Socket::Connect(SockAddrIn addr_in) {
+ const sockaddr host_addr_in = TranslateFromSockAddrIn(addr_in);
+ if (connect(fd, &host_addr_in, sizeof(host_addr_in)) != SOCKET_ERROR) {
+ return Errno::SUCCESS;
+ }
+
+ return GetAndLogLastError();
+}
+
+std::pair<SockAddrIn, Errno> Socket::GetPeerName() {
+ sockaddr addr;
+ socklen_t addrlen = sizeof(addr);
+ if (getpeername(fd, &addr, &addrlen) == SOCKET_ERROR) {
+ return {SockAddrIn{}, GetAndLogLastError()};
+ }
+
+ ASSERT(addrlen == sizeof(sockaddr_in));
+ return {TranslateToSockAddrIn(addr), Errno::SUCCESS};
+}
+
+std::pair<SockAddrIn, Errno> Socket::GetSockName() {
+ sockaddr addr;
+ socklen_t addrlen = sizeof(addr);
+ if (getsockname(fd, &addr, &addrlen) == SOCKET_ERROR) {
+ return {SockAddrIn{}, GetAndLogLastError()};
+ }
+
+ ASSERT(addrlen == sizeof(sockaddr_in));
+ return {TranslateToSockAddrIn(addr), Errno::SUCCESS};
+}
+
+Errno Socket::Bind(SockAddrIn addr) {
+ const sockaddr addr_in = TranslateFromSockAddrIn(addr);
+ if (bind(fd, &addr_in, sizeof(addr_in)) != SOCKET_ERROR) {
+ return Errno::SUCCESS;
+ }
+
+ return GetAndLogLastError();
+}
+
+Errno Socket::Listen(s32 backlog) {
+ if (listen(fd, backlog) != SOCKET_ERROR) {
+ return Errno::SUCCESS;
+ }
+
+ return GetAndLogLastError();
+}
+
+Errno Socket::Shutdown(ShutdownHow how) {
+ int host_how = 0;
+ switch (how) {
+ case ShutdownHow::RD:
+ host_how = SD_RECEIVE;
+ break;
+ case ShutdownHow::WR:
+ host_how = SD_SEND;
+ break;
+ case ShutdownHow::RDWR:
+ host_how = SD_BOTH;
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented flag how={}", how);
+ return Errno::SUCCESS;
+ }
+ if (shutdown(fd, host_how) != SOCKET_ERROR) {
+ return Errno::SUCCESS;
+ }
+
+ return GetAndLogLastError();
+}
+
+std::pair<s32, Errno> Socket::Recv(int flags, std::vector<u8>& message) {
+ ASSERT(flags == 0);
+ ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
+
+ const auto result =
+ recv(fd, reinterpret_cast<char*>(message.data()), static_cast<int>(message.size()), 0);
+ if (result != SOCKET_ERROR) {
+ return {static_cast<s32>(result), Errno::SUCCESS};
+ }
+
+ return {-1, GetAndLogLastError()};
+}
+
+std::pair<s32, Errno> Socket::RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) {
+ ASSERT(flags == 0);
+ ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
+
+ sockaddr addr_in{};
+ socklen_t addrlen = sizeof(addr_in);
+ socklen_t* const p_addrlen = addr ? &addrlen : nullptr;
+ sockaddr* const p_addr_in = addr ? &addr_in : nullptr;
+
+ const auto result = recvfrom(fd, reinterpret_cast<char*>(message.data()),
+ static_cast<int>(message.size()), 0, p_addr_in, p_addrlen);
+ if (result != SOCKET_ERROR) {
+ if (addr) {
+ ASSERT(addrlen == sizeof(addr_in));
+ *addr = TranslateToSockAddrIn(addr_in);
+ }
+ return {static_cast<s32>(result), Errno::SUCCESS};
+ }
+
+ return {-1, GetAndLogLastError()};
+}
+
+std::pair<s32, Errno> Socket::Send(const std::vector<u8>& message, int flags) {
+ ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
+ ASSERT(flags == 0);
+
+ const auto result = send(fd, reinterpret_cast<const char*>(message.data()),
+ static_cast<int>(message.size()), 0);
+ if (result != SOCKET_ERROR) {
+ return {static_cast<s32>(result), Errno::SUCCESS};
+ }
+
+ return {-1, GetAndLogLastError()};
+}
+
+std::pair<s32, Errno> Socket::SendTo(u32 flags, const std::vector<u8>& message,
+ const SockAddrIn* addr) {
+ ASSERT(flags == 0);
+
+ const sockaddr* to = nullptr;
+ const int tolen = addr ? sizeof(sockaddr) : 0;
+ sockaddr host_addr_in;
+
+ if (addr) {
+ host_addr_in = TranslateFromSockAddrIn(*addr);
+ to = &host_addr_in;
+ }
+
+ const auto result = sendto(fd, reinterpret_cast<const char*>(message.data()),
+ static_cast<int>(message.size()), 0, to, tolen);
+ if (result != SOCKET_ERROR) {
+ return {static_cast<s32>(result), Errno::SUCCESS};
+ }
+
+ return {-1, GetAndLogLastError()};
+}
+
+Errno Socket::Close() {
+ [[maybe_unused]] const int result = closesocket(fd);
+ ASSERT(result == 0);
+ fd = INVALID_SOCKET;
+
+ return Errno::SUCCESS;
+}
+
+Errno Socket::SetLinger(bool enable, u32 linger) {
+ return SetSockOpt(fd, SO_LINGER, MakeLinger(enable, linger));
+}
+
+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);
+}
+
+Errno Socket::SetSndBuf(u32 value) {
+ return SetSockOpt(fd, SO_SNDBUF, value);
+}
+
+Errno Socket::SetRcvBuf(u32 value) {
+ return SetSockOpt(fd, SO_RCVBUF, value);
+}
+
+Errno Socket::SetSndTimeo(u32 value) {
+ return SetSockOpt(fd, SO_SNDTIMEO, value);
+}
+
+Errno Socket::SetRcvTimeo(u32 value) {
+ return SetSockOpt(fd, SO_RCVTIMEO, value);
+}
+
+Errno Socket::SetNonBlock(bool enable) {
+ if (EnableNonBlock(fd, enable)) {
+ return Errno::SUCCESS;
+ }
+ return GetAndLogLastError();
+}
+
+bool Socket::IsOpened() const {
+ return fd != INVALID_SOCKET;
+}
+
+void Socket::HandleProxyPacket(const ProxyPacket& packet) {
+ LOG_WARNING(Network, "ProxyPacket received, but not in Proxy mode!");
+}
+
+} // namespace Network
diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h
new file mode 100644
index 000000000..36994c22e
--- /dev/null
+++ b/src/core/internal_network/network.h
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <optional>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/socket_types.h"
+
+#ifdef _WIN32
+#include <winsock2.h>
+#elif YUZU_UNIX
+#include <netinet/in.h>
+#endif
+
+namespace Network {
+
+class SocketBase;
+class Socket;
+
+/// Error code for network functions
+enum class Errno {
+ SUCCESS,
+ BADF,
+ INVAL,
+ MFILE,
+ NOTCONN,
+ AGAIN,
+ CONNREFUSED,
+ HOSTUNREACH,
+ NETDOWN,
+ NETUNREACH,
+ TIMEDOUT,
+ MSGSIZE,
+ OTHER,
+};
+
+/// Cross-platform poll fd structure
+
+enum class PollEvents : u16 {
+ // Using Pascal case because IN is a macro on Windows.
+ In = 1 << 0,
+ Pri = 1 << 1,
+ Out = 1 << 2,
+ Err = 1 << 3,
+ Hup = 1 << 4,
+ Nval = 1 << 5,
+};
+
+DECLARE_ENUM_FLAG_OPERATORS(PollEvents);
+
+struct PollFD {
+ SocketBase* socket;
+ PollEvents events;
+ PollEvents revents;
+};
+
+class NetworkInstance {
+public:
+ explicit NetworkInstance();
+ ~NetworkInstance();
+};
+
+#ifdef _WIN32
+constexpr IPv4Address TranslateIPv4(in_addr addr) {
+ auto& bytes = addr.S_un.S_un_b;
+ return IPv4Address{bytes.s_b1, bytes.s_b2, bytes.s_b3, bytes.s_b4};
+}
+#elif YUZU_UNIX
+constexpr IPv4Address TranslateIPv4(in_addr addr) {
+ const u32 bytes = addr.s_addr;
+ return IPv4Address{static_cast<u8>(bytes), static_cast<u8>(bytes >> 8),
+ static_cast<u8>(bytes >> 16), static_cast<u8>(bytes >> 24)};
+}
+#endif
+
+/// @brief Returns host's IPv4 address
+/// @return human ordered IPv4 address (e.g. 192.168.0.1) as an array
+std::optional<IPv4Address> GetHostIPv4Address();
+
+} // namespace Network
diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp
new file mode 100644
index 000000000..057fd3661
--- /dev/null
+++ b/src/core/internal_network/network_interface.cpp
@@ -0,0 +1,219 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <fstream>
+#include <sstream>
+#include <vector>
+
+#include "common/bit_cast.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "common/string_util.h"
+#include "core/internal_network/network_interface.h"
+
+#ifdef _WIN32
+#include <iphlpapi.h>
+#else
+#include <cerrno>
+#include <ifaddrs.h>
+#include <net/if.h>
+#endif
+
+namespace Network {
+
+#ifdef _WIN32
+
+std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
+ std::vector<IP_ADAPTER_ADDRESSES> adapter_addresses;
+ DWORD ret = ERROR_BUFFER_OVERFLOW;
+ DWORD buf_size = 0;
+
+ // retry up to 5 times
+ for (int i = 0; i < 5 && ret == ERROR_BUFFER_OVERFLOW; i++) {
+ ret = GetAdaptersAddresses(
+ AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS,
+ nullptr, adapter_addresses.data(), &buf_size);
+
+ if (ret != ERROR_BUFFER_OVERFLOW) {
+ break;
+ }
+
+ adapter_addresses.resize((buf_size / sizeof(IP_ADAPTER_ADDRESSES)) + 1);
+ }
+
+ if (ret != NO_ERROR) {
+ LOG_ERROR(Network, "Failed to get network interfaces with GetAdaptersAddresses");
+ return {};
+ }
+
+ std::vector<NetworkInterface> result;
+
+ for (auto current_address = adapter_addresses.data(); current_address != nullptr;
+ current_address = current_address->Next) {
+ if (current_address->FirstUnicastAddress == nullptr ||
+ current_address->FirstUnicastAddress->Address.lpSockaddr == nullptr) {
+ continue;
+ }
+
+ if (current_address->OperStatus != IfOperStatusUp) {
+ continue;
+ }
+
+ const auto ip_addr = Common::BitCast<struct sockaddr_in>(
+ *current_address->FirstUnicastAddress->Address.lpSockaddr)
+ .sin_addr;
+
+ ULONG mask = 0;
+ if (ConvertLengthToIpv4Mask(current_address->FirstUnicastAddress->OnLinkPrefixLength,
+ &mask) != NO_ERROR) {
+ LOG_ERROR(Network, "Failed to convert IPv4 prefix length to subnet mask");
+ continue;
+ }
+
+ struct in_addr gateway = {.S_un{.S_addr{0}}};
+ if (current_address->FirstGatewayAddress != nullptr &&
+ current_address->FirstGatewayAddress->Address.lpSockaddr != nullptr) {
+ gateway = Common::BitCast<struct sockaddr_in>(
+ *current_address->FirstGatewayAddress->Address.lpSockaddr)
+ .sin_addr;
+ }
+
+ result.emplace_back(NetworkInterface{
+ .name{Common::UTF16ToUTF8(std::wstring{current_address->FriendlyName})},
+ .ip_address{ip_addr},
+ .subnet_mask = in_addr{.S_un{.S_addr{mask}}},
+ .gateway = gateway});
+ }
+
+ return result;
+}
+
+#else
+
+std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
+ struct ifaddrs* ifaddr = nullptr;
+
+ if (getifaddrs(&ifaddr) != 0) {
+ LOG_ERROR(Network, "Failed to get network interfaces with getifaddrs: {}",
+ std::strerror(errno));
+ return {};
+ }
+
+ std::vector<NetworkInterface> result;
+
+ for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == nullptr || ifa->ifa_netmask == nullptr) {
+ continue;
+ }
+
+ if (ifa->ifa_addr->sa_family != AF_INET) {
+ continue;
+ }
+
+ if ((ifa->ifa_flags & IFF_UP) == 0 || (ifa->ifa_flags & IFF_LOOPBACK) != 0) {
+ continue;
+ }
+
+ u32 gateway{};
+
+ std::ifstream file{"/proc/net/route"};
+ if (!file.is_open()) {
+ LOG_ERROR(Network, "Failed to open \"/proc/net/route\"");
+
+ result.emplace_back(NetworkInterface{
+ .name{ifa->ifa_name},
+ .ip_address{Common::BitCast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr},
+ .subnet_mask{Common::BitCast<struct sockaddr_in>(*ifa->ifa_netmask).sin_addr},
+ .gateway{in_addr{.s_addr = gateway}}});
+ continue;
+ }
+
+ // ignore header
+ file.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
+
+ bool gateway_found = false;
+
+ for (std::string line; std::getline(file, line);) {
+ std::istringstream iss{line};
+
+ std::string iface_name;
+ iss >> iface_name;
+ if (iface_name != ifa->ifa_name) {
+ continue;
+ }
+
+ iss >> std::hex;
+
+ u32 dest{};
+ iss >> dest;
+ if (dest != 0) {
+ // not the default route
+ continue;
+ }
+
+ iss >> gateway;
+
+ u16 flags{};
+ iss >> flags;
+
+ // flag RTF_GATEWAY (defined in <linux/route.h>)
+ if ((flags & 0x2) == 0) {
+ continue;
+ }
+
+ gateway_found = true;
+ break;
+ }
+
+ if (!gateway_found) {
+ gateway = 0;
+ }
+
+ result.emplace_back(NetworkInterface{
+ .name{ifa->ifa_name},
+ .ip_address{Common::BitCast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr},
+ .subnet_mask{Common::BitCast<struct sockaddr_in>(*ifa->ifa_netmask).sin_addr},
+ .gateway{in_addr{.s_addr = gateway}}});
+ }
+
+ freeifaddrs(ifaddr);
+
+ return result;
+}
+
+#endif
+
+std::optional<NetworkInterface> GetSelectedNetworkInterface() {
+ const auto& selected_network_interface = Settings::values.network_interface.GetValue();
+ const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
+ if (network_interfaces.empty()) {
+ LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces");
+ return std::nullopt;
+ }
+
+ const auto res =
+ std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) {
+ return iface.name == selected_network_interface;
+ });
+
+ if (res == network_interfaces.end()) {
+ LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface);
+ return std::nullopt;
+ }
+
+ return *res;
+}
+
+void SelectFirstNetworkInterface() {
+ const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
+
+ if (network_interfaces.empty()) {
+ return;
+ }
+
+ Settings::values.network_interface.SetValue(network_interfaces[0].name);
+}
+
+} // namespace Network
diff --git a/src/core/internal_network/network_interface.h b/src/core/internal_network/network_interface.h
new file mode 100644
index 000000000..175e61b1f
--- /dev/null
+++ b/src/core/internal_network/network_interface.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <optional>
+#include <string>
+#include <vector>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <netinet/in.h>
+#endif
+
+namespace Network {
+
+struct NetworkInterface {
+ std::string name;
+ struct in_addr ip_address;
+ struct in_addr subnet_mask;
+ struct in_addr gateway;
+};
+
+std::vector<NetworkInterface> GetAvailableNetworkInterfaces();
+std::optional<NetworkInterface> GetSelectedNetworkInterface();
+void SelectFirstNetworkInterface();
+
+} // namespace Network
diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp
new file mode 100644
index 000000000..7d5d37bbc
--- /dev/null
+++ b/src/core/internal_network/socket_proxy.cpp
@@ -0,0 +1,296 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <chrono>
+#include <thread>
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/zstd_compression.h"
+#include "core/internal_network/network.h"
+#include "core/internal_network/network_interface.h"
+#include "core/internal_network/socket_proxy.h"
+
+namespace Network {
+
+ProxySocket::ProxySocket(RoomNetwork& room_network_) noexcept : room_network{room_network_} {}
+
+ProxySocket::~ProxySocket() {
+ if (fd == INVALID_SOCKET) {
+ return;
+ }
+ fd = INVALID_SOCKET;
+}
+
+void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) {
+ if (protocol != packet.protocol || local_endpoint.portno != packet.remote_endpoint.portno ||
+ closed) {
+ return;
+ }
+
+ if (!broadcast && packet.broadcast) {
+ LOG_INFO(Network, "Received broadcast packet, but not configured for broadcast mode");
+ return;
+ }
+
+ auto decompressed = packet;
+ decompressed.data = Common::Compression::DecompressDataZSTD(packet.data);
+
+ std::lock_guard guard(packets_mutex);
+ received_packets.push(decompressed);
+}
+
+template <typename T>
+Errno ProxySocket::SetSockOpt(SOCKET fd_, int option, T value) {
+ LOG_DEBUG(Network, "(STUBBED) called");
+ return Errno::SUCCESS;
+}
+
+Errno ProxySocket::Initialize(Domain domain, Type type, Protocol socket_protocol) {
+ protocol = socket_protocol;
+ SetSockOpt(fd, SO_TYPE, type);
+
+ return Errno::SUCCESS;
+}
+
+std::pair<ProxySocket::AcceptResult, Errno> ProxySocket::Accept() {
+ LOG_WARNING(Network, "(STUBBED) called");
+ return {AcceptResult{}, Errno::SUCCESS};
+}
+
+Errno ProxySocket::Connect(SockAddrIn addr_in) {
+ LOG_WARNING(Network, "(STUBBED) called");
+ return Errno::SUCCESS;
+}
+
+std::pair<SockAddrIn, Errno> ProxySocket::GetPeerName() {
+ LOG_WARNING(Network, "(STUBBED) called");
+ return {SockAddrIn{}, Errno::SUCCESS};
+}
+
+std::pair<SockAddrIn, Errno> ProxySocket::GetSockName() {
+ LOG_WARNING(Network, "(STUBBED) called");
+ return {SockAddrIn{}, Errno::SUCCESS};
+}
+
+Errno ProxySocket::Bind(SockAddrIn addr) {
+ if (is_bound) {
+ LOG_WARNING(Network, "Rebinding Socket is unimplemented!");
+ return Errno::SUCCESS;
+ }
+ local_endpoint = addr;
+ is_bound = true;
+
+ return Errno::SUCCESS;
+}
+
+Errno ProxySocket::Listen(s32 backlog) {
+ LOG_WARNING(Network, "(STUBBED) called");
+ return Errno::SUCCESS;
+}
+
+Errno ProxySocket::Shutdown(ShutdownHow how) {
+ LOG_WARNING(Network, "(STUBBED) called");
+ return Errno::SUCCESS;
+}
+
+std::pair<s32, Errno> ProxySocket::Recv(int flags, std::vector<u8>& message) {
+ LOG_WARNING(Network, "(STUBBED) called");
+ ASSERT(flags == 0);
+ ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
+
+ return {static_cast<s32>(0), Errno::SUCCESS};
+}
+
+std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) {
+ ASSERT(flags == 0);
+ ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
+
+ // TODO (flTobi): Verify the timeout behavior and break when connection is lost
+ const auto timestamp = std::chrono::steady_clock::now();
+ // When receive_timeout is set to zero, the socket is supposed to wait indefinitely until a
+ // packet arrives. In order to prevent lost packets from hanging the emulation thread, we set
+ // the timeout to 5s instead
+ const auto timeout = receive_timeout == 0 ? 5000 : receive_timeout;
+ while (true) {
+ {
+ std::lock_guard guard(packets_mutex);
+ if (received_packets.size() > 0) {
+ return ReceivePacket(flags, message, addr, message.size());
+ }
+ }
+
+ if (!blocking) {
+ return {-1, Errno::AGAIN};
+ }
+
+ std::this_thread::yield();
+
+ const auto time_diff = std::chrono::steady_clock::now() - timestamp;
+ const auto time_diff_ms =
+ std::chrono::duration_cast<std::chrono::milliseconds>(time_diff).count();
+
+ if (time_diff_ms > timeout) {
+ return {-1, Errno::TIMEDOUT};
+ }
+ }
+}
+
+std::pair<s32, Errno> ProxySocket::ReceivePacket(int flags, std::vector<u8>& message,
+ SockAddrIn* addr, std::size_t max_length) {
+ ProxyPacket& packet = received_packets.front();
+ if (addr) {
+ addr->family = Domain::INET;
+ addr->ip = packet.local_endpoint.ip; // The senders ip address
+ addr->portno = packet.local_endpoint.portno; // The senders port number
+ }
+
+ bool peek = (flags & FLAG_MSG_PEEK) != 0;
+ std::size_t read_bytes;
+ if (packet.data.size() > max_length) {
+ read_bytes = max_length;
+ message.clear();
+ std::copy(packet.data.begin(), packet.data.begin() + read_bytes,
+ std::back_inserter(message));
+ message.resize(max_length);
+
+ if (protocol == Protocol::UDP) {
+ if (!peek) {
+ received_packets.pop();
+ }
+ return {-1, Errno::MSGSIZE};
+ } else if (protocol == Protocol::TCP) {
+ std::vector<u8> numArray(packet.data.size() - max_length);
+ std::copy(packet.data.begin() + max_length, packet.data.end(),
+ std::back_inserter(numArray));
+ packet.data = numArray;
+ }
+ } else {
+ read_bytes = packet.data.size();
+ message.clear();
+ std::copy(packet.data.begin(), packet.data.end(), std::back_inserter(message));
+ message.resize(max_length);
+ if (!peek) {
+ received_packets.pop();
+ }
+ }
+
+ return {static_cast<u32>(read_bytes), Errno::SUCCESS};
+}
+
+std::pair<s32, Errno> ProxySocket::Send(const std::vector<u8>& message, int flags) {
+ LOG_WARNING(Network, "(STUBBED) called");
+ ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
+ ASSERT(flags == 0);
+
+ return {static_cast<s32>(0), Errno::SUCCESS};
+}
+
+void ProxySocket::SendPacket(ProxyPacket& packet) {
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(),
+ packet.data.size());
+ room_member->SendProxyPacket(packet);
+ }
+ }
+}
+
+std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, const std::vector<u8>& message,
+ const SockAddrIn* addr) {
+ ASSERT(flags == 0);
+
+ if (!is_bound) {
+ LOG_ERROR(Network, "ProxySocket is not bound!");
+ return {static_cast<s32>(message.size()), Errno::SUCCESS};
+ }
+
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ if (!room_member->IsConnected()) {
+ return {static_cast<s32>(message.size()), Errno::SUCCESS};
+ }
+ }
+
+ ProxyPacket packet;
+ packet.local_endpoint = local_endpoint;
+ packet.remote_endpoint = *addr;
+ packet.protocol = protocol;
+ packet.broadcast = broadcast && packet.remote_endpoint.ip[3] == 255;
+
+ auto& ip = local_endpoint.ip;
+ auto ipv4 = Network::GetHostIPv4Address();
+ // If the ip is all zeroes (INADDR_ANY) or if it matches the hosts ip address,
+ // replace it with a "fake" routing address
+ if (std::all_of(ip.begin(), ip.end(), [](u8 i) { return i == 0; }) || (ipv4 && ipv4 == ip)) {
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ packet.local_endpoint.ip = room_member->GetFakeIpAddress();
+ }
+ }
+
+ packet.data.clear();
+ std::copy(message.begin(), message.end(), std::back_inserter(packet.data));
+
+ SendPacket(packet);
+
+ return {static_cast<s32>(message.size()), Errno::SUCCESS};
+}
+
+Errno ProxySocket::Close() {
+ fd = INVALID_SOCKET;
+ closed = true;
+
+ return Errno::SUCCESS;
+}
+
+Errno ProxySocket::SetLinger(bool enable, u32 linger) {
+ struct Linger {
+ u16 linger_enable;
+ u16 linger_time;
+ } values;
+ values.linger_enable = enable ? 1 : 0;
+ values.linger_time = static_cast<u16>(linger);
+
+ return SetSockOpt(fd, SO_LINGER, values);
+}
+
+Errno ProxySocket::SetReuseAddr(bool enable) {
+ return SetSockOpt<u32>(fd, SO_REUSEADDR, enable ? 1 : 0);
+}
+
+Errno ProxySocket::SetBroadcast(bool enable) {
+ broadcast = enable;
+ return SetSockOpt<u32>(fd, SO_BROADCAST, enable ? 1 : 0);
+}
+
+Errno ProxySocket::SetSndBuf(u32 value) {
+ return SetSockOpt(fd, SO_SNDBUF, value);
+}
+
+Errno ProxySocket::SetKeepAlive(bool enable) {
+ return Errno::SUCCESS;
+}
+
+Errno ProxySocket::SetRcvBuf(u32 value) {
+ return SetSockOpt(fd, SO_RCVBUF, value);
+}
+
+Errno ProxySocket::SetSndTimeo(u32 value) {
+ send_timeout = value;
+ return SetSockOpt(fd, SO_SNDTIMEO, static_cast<int>(value));
+}
+
+Errno ProxySocket::SetRcvTimeo(u32 value) {
+ receive_timeout = value;
+ return SetSockOpt(fd, SO_RCVTIMEO, static_cast<int>(value));
+}
+
+Errno ProxySocket::SetNonBlock(bool enable) {
+ blocking = !enable;
+ return Errno::SUCCESS;
+}
+
+bool ProxySocket::IsOpened() const {
+ return fd != INVALID_SOCKET;
+}
+
+} // namespace Network
diff --git a/src/core/internal_network/socket_proxy.h b/src/core/internal_network/socket_proxy.h
new file mode 100644
index 000000000..f12b5f567
--- /dev/null
+++ b/src/core/internal_network/socket_proxy.h
@@ -0,0 +1,97 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+#include <vector>
+#include <queue>
+
+#include "common/common_funcs.h"
+#include "core/internal_network/sockets.h"
+#include "network/network.h"
+
+namespace Network {
+
+class ProxySocket : public SocketBase {
+public:
+ YUZU_NON_COPYABLE(ProxySocket);
+ YUZU_NON_MOVEABLE(ProxySocket);
+
+ explicit ProxySocket(RoomNetwork& room_network_) noexcept;
+ ~ProxySocket() override;
+
+ void HandleProxyPacket(const ProxyPacket& packet) override;
+
+ Errno Initialize(Domain domain, Type type, Protocol socket_protocol) override;
+
+ Errno Close() override;
+
+ std::pair<AcceptResult, Errno> Accept() override;
+
+ Errno Connect(SockAddrIn addr_in) override;
+
+ std::pair<SockAddrIn, Errno> GetPeerName() override;
+
+ std::pair<SockAddrIn, Errno> GetSockName() override;
+
+ Errno Bind(SockAddrIn addr) override;
+
+ Errno Listen(s32 backlog) override;
+
+ Errno Shutdown(ShutdownHow how) override;
+
+ std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) override;
+
+ std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) override;
+
+ std::pair<s32, Errno> ReceivePacket(int flags, std::vector<u8>& message, SockAddrIn* addr,
+ std::size_t max_length);
+
+ std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) override;
+
+ void SendPacket(ProxyPacket& packet);
+
+ std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message,
+ const SockAddrIn* addr) override;
+
+ Errno SetLinger(bool enable, u32 linger) override;
+
+ Errno SetReuseAddr(bool enable) override;
+
+ Errno SetBroadcast(bool enable) override;
+
+ Errno SetKeepAlive(bool enable) override;
+
+ Errno SetSndBuf(u32 value) override;
+
+ Errno SetRcvBuf(u32 value) override;
+
+ Errno SetSndTimeo(u32 value) override;
+
+ Errno SetRcvTimeo(u32 value) override;
+
+ Errno SetNonBlock(bool enable) override;
+
+ template <typename T>
+ Errno SetSockOpt(SOCKET fd, int option, T value);
+
+ bool IsOpened() const override;
+
+private:
+ bool broadcast = false;
+ bool closed = false;
+ u32 send_timeout = 0;
+ u32 receive_timeout = 0;
+ bool is_bound = false;
+ SockAddrIn local_endpoint{};
+ bool blocking = true;
+ std::queue<ProxyPacket> received_packets;
+ Protocol protocol;
+
+ std::mutex packets_mutex;
+
+ RoomNetwork& room_network;
+};
+
+} // namespace Network
diff --git a/src/core/internal_network/sockets.h b/src/core/internal_network/sockets.h
new file mode 100644
index 000000000..2e328c645
--- /dev/null
+++ b/src/core/internal_network/sockets.h
@@ -0,0 +1,174 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <utility>
+
+#if defined(_WIN32)
+#elif !YUZU_UNIX
+#error "Platform not implemented"
+#endif
+
+#include "common/common_types.h"
+#include "core/internal_network/network.h"
+#include "network/network.h"
+
+// TODO: C++20 Replace std::vector usages with std::span
+
+namespace Network {
+
+class SocketBase {
+public:
+#ifdef YUZU_UNIX
+ using SOCKET = int;
+ static constexpr SOCKET INVALID_SOCKET = -1;
+ static constexpr SOCKET SOCKET_ERROR = -1;
+#endif
+
+ struct AcceptResult {
+ std::unique_ptr<SocketBase> socket;
+ SockAddrIn sockaddr_in;
+ };
+
+ SocketBase() = default;
+ explicit SocketBase(SOCKET fd_) : fd{fd_} {}
+
+ virtual ~SocketBase() = default;
+
+ virtual SocketBase& operator=(const SocketBase&) = delete;
+
+ // Avoid closing sockets implicitly
+ virtual SocketBase& operator=(SocketBase&&) noexcept = delete;
+
+ virtual Errno Initialize(Domain domain, Type type, Protocol protocol) = 0;
+
+ virtual Errno Close() = 0;
+
+ virtual std::pair<AcceptResult, Errno> Accept() = 0;
+
+ virtual Errno Connect(SockAddrIn addr_in) = 0;
+
+ virtual std::pair<SockAddrIn, Errno> GetPeerName() = 0;
+
+ virtual std::pair<SockAddrIn, Errno> GetSockName() = 0;
+
+ virtual Errno Bind(SockAddrIn addr) = 0;
+
+ virtual Errno Listen(s32 backlog) = 0;
+
+ virtual Errno Shutdown(ShutdownHow how) = 0;
+
+ virtual std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) = 0;
+
+ virtual std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message,
+ SockAddrIn* addr) = 0;
+
+ virtual std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) = 0;
+
+ virtual std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message,
+ const SockAddrIn* addr) = 0;
+
+ virtual Errno SetLinger(bool enable, u32 linger) = 0;
+
+ virtual Errno SetReuseAddr(bool enable) = 0;
+
+ virtual Errno SetKeepAlive(bool enable) = 0;
+
+ virtual Errno SetBroadcast(bool enable) = 0;
+
+ virtual Errno SetSndBuf(u32 value) = 0;
+
+ virtual Errno SetRcvBuf(u32 value) = 0;
+
+ virtual Errno SetSndTimeo(u32 value) = 0;
+
+ virtual Errno SetRcvTimeo(u32 value) = 0;
+
+ virtual Errno SetNonBlock(bool enable) = 0;
+
+ virtual bool IsOpened() const = 0;
+
+ virtual void HandleProxyPacket(const ProxyPacket& packet) = 0;
+
+ [[nodiscard]] SOCKET GetFD() const {
+ return fd;
+ }
+
+protected:
+ SOCKET fd = INVALID_SOCKET;
+};
+
+class Socket : public SocketBase {
+public:
+ Socket() = default;
+ explicit Socket(SOCKET fd_) : SocketBase{fd_} {}
+
+ ~Socket() override;
+
+ Socket(const Socket&) = delete;
+ Socket& operator=(const Socket&) = delete;
+
+ Socket(Socket&& rhs) noexcept;
+
+ // Avoid closing sockets implicitly
+ Socket& operator=(Socket&&) noexcept = delete;
+
+ Errno Initialize(Domain domain, Type type, Protocol protocol) override;
+
+ Errno Close() override;
+
+ std::pair<AcceptResult, Errno> Accept() override;
+
+ Errno Connect(SockAddrIn addr_in) override;
+
+ std::pair<SockAddrIn, Errno> GetPeerName() override;
+
+ std::pair<SockAddrIn, Errno> GetSockName() override;
+
+ Errno Bind(SockAddrIn addr) override;
+
+ Errno Listen(s32 backlog) override;
+
+ Errno Shutdown(ShutdownHow how) override;
+
+ std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) override;
+
+ std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) override;
+
+ std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) override;
+
+ std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message,
+ const SockAddrIn* addr) override;
+
+ Errno SetLinger(bool enable, u32 linger) override;
+
+ Errno SetReuseAddr(bool enable) override;
+
+ Errno SetKeepAlive(bool enable) override;
+
+ Errno SetBroadcast(bool enable) override;
+
+ Errno SetSndBuf(u32 value) override;
+
+ Errno SetRcvBuf(u32 value) override;
+
+ Errno SetSndTimeo(u32 value) override;
+
+ Errno SetRcvTimeo(u32 value) override;
+
+ Errno SetNonBlock(bool enable) override;
+
+ template <typename T>
+ Errno SetSockOpt(SOCKET fd, int option, T value);
+
+ bool IsOpened() const override;
+
+ void HandleProxyPacket(const ProxyPacket& packet) override;
+};
+
+std::pair<s32, Errno> Poll(std::vector<PollFD>& poll_fds, s32 timeout);
+
+} // namespace Network
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index b47e3bf69..192571d35 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/logging/log.h"
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h
index 79a4d4db5..f7702225e 100644
--- a/src/core/loader/deconstructed_rom_directory.h
+++ b/src/core/loader/deconstructed_rom_directory.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
deleted file mode 100644
index d0250bdb4..000000000
--- a/src/core/loader/elf.cpp
+++ /dev/null
@@ -1,414 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <cstring>
-#include <memory>
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "core/hle/kernel/code_set.h"
-#include "core/hle/kernel/k_page_table.h"
-#include "core/hle/kernel/k_process.h"
-#include "core/loader/elf.h"
-#include "core/memory.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// ELF Header Constants
-
-// File type
-enum ElfType {
- ET_NONE = 0,
- ET_REL = 1,
- ET_EXEC = 2,
- ET_DYN = 3,
- ET_CORE = 4,
- ET_LOPROC = 0xFF00,
- ET_HIPROC = 0xFFFF,
-};
-
-// Machine/Architecture
-enum ElfMachine {
- EM_NONE = 0,
- EM_M32 = 1,
- EM_SPARC = 2,
- EM_386 = 3,
- EM_68K = 4,
- EM_88K = 5,
- EM_860 = 7,
- EM_MIPS = 8
-};
-
-// File version
-#define EV_NONE 0
-#define EV_CURRENT 1
-
-// Identification index
-#define EI_MAG0 0
-#define EI_MAG1 1
-#define EI_MAG2 2
-#define EI_MAG3 3
-#define EI_CLASS 4
-#define EI_DATA 5
-#define EI_VERSION 6
-#define EI_PAD 7
-#define EI_NIDENT 16
-
-// Sections constants
-
-// Section types
-#define SHT_NULL 0
-#define SHT_PROGBITS 1
-#define SHT_SYMTAB 2
-#define SHT_STRTAB 3
-#define SHT_RELA 4
-#define SHT_HASH 5
-#define SHT_DYNAMIC 6
-#define SHT_NOTE 7
-#define SHT_NOBITS 8
-#define SHT_REL 9
-#define SHT_SHLIB 10
-#define SHT_DYNSYM 11
-#define SHT_LOPROC 0x70000000
-#define SHT_HIPROC 0x7FFFFFFF
-#define SHT_LOUSER 0x80000000
-#define SHT_HIUSER 0xFFFFFFFF
-
-// Section flags
-enum ElfSectionFlags {
- SHF_WRITE = 0x1,
- SHF_ALLOC = 0x2,
- SHF_EXECINSTR = 0x4,
- SHF_MASKPROC = 0xF0000000,
-};
-
-// Segment types
-#define PT_NULL 0
-#define PT_LOAD 1
-#define PT_DYNAMIC 2
-#define PT_INTERP 3
-#define PT_NOTE 4
-#define PT_SHLIB 5
-#define PT_PHDR 6
-#define PT_LOPROC 0x70000000
-#define PT_HIPROC 0x7FFFFFFF
-
-// Segment flags
-#define PF_X 0x1
-#define PF_W 0x2
-#define PF_R 0x4
-#define PF_MASKPROC 0xF0000000
-
-typedef unsigned int Elf32_Addr;
-typedef unsigned short Elf32_Half;
-typedef unsigned int Elf32_Off;
-typedef signed int Elf32_Sword;
-typedef unsigned int Elf32_Word;
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// ELF file header
-
-struct Elf32_Ehdr {
- unsigned char e_ident[EI_NIDENT];
- Elf32_Half e_type;
- Elf32_Half e_machine;
- Elf32_Word e_version;
- Elf32_Addr e_entry;
- Elf32_Off e_phoff;
- Elf32_Off e_shoff;
- Elf32_Word e_flags;
- Elf32_Half e_ehsize;
- Elf32_Half e_phentsize;
- Elf32_Half e_phnum;
- Elf32_Half e_shentsize;
- Elf32_Half e_shnum;
- Elf32_Half e_shstrndx;
-};
-
-// Section header
-struct Elf32_Shdr {
- Elf32_Word sh_name;
- Elf32_Word sh_type;
- Elf32_Word sh_flags;
- Elf32_Addr sh_addr;
- Elf32_Off sh_offset;
- Elf32_Word sh_size;
- Elf32_Word sh_link;
- Elf32_Word sh_info;
- Elf32_Word sh_addralign;
- Elf32_Word sh_entsize;
-};
-
-// Segment header
-struct Elf32_Phdr {
- Elf32_Word p_type;
- Elf32_Off p_offset;
- Elf32_Addr p_vaddr;
- Elf32_Addr p_paddr;
- Elf32_Word p_filesz;
- Elf32_Word p_memsz;
- Elf32_Word p_flags;
- Elf32_Word p_align;
-};
-
-// Symbol table entry
-struct Elf32_Sym {
- Elf32_Word st_name;
- Elf32_Addr st_value;
- Elf32_Word st_size;
- unsigned char st_info;
- unsigned char st_other;
- Elf32_Half st_shndx;
-};
-
-// Relocation entries
-struct Elf32_Rel {
- Elf32_Addr r_offset;
- Elf32_Word r_info;
-};
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// ElfReader class
-
-typedef int SectionID;
-
-class ElfReader {
-private:
- char* base;
- u32* base32;
-
- Elf32_Ehdr* header;
- Elf32_Phdr* segments;
- Elf32_Shdr* sections;
-
- u32* sectionAddrs;
- bool relocate;
- VAddr entryPoint;
-
-public:
- explicit ElfReader(void* ptr);
-
- u32 Read32(int off) const {
- return base32[off >> 2];
- }
-
- // Quick accessors
- ElfType GetType() const {
- return (ElfType)(header->e_type);
- }
- ElfMachine GetMachine() const {
- return (ElfMachine)(header->e_machine);
- }
- VAddr GetEntryPoint() const {
- return entryPoint;
- }
- u32 GetFlags() const {
- return (u32)(header->e_flags);
- }
- Kernel::CodeSet LoadInto(VAddr vaddr);
-
- int GetNumSegments() const {
- return (int)(header->e_phnum);
- }
- int GetNumSections() const {
- return (int)(header->e_shnum);
- }
- const u8* GetPtr(int offset) const {
- return (u8*)base + offset;
- }
- const char* GetSectionName(int section) const;
- const u8* GetSectionDataPtr(int section) const {
- if (section < 0 || section >= header->e_shnum)
- return nullptr;
- if (sections[section].sh_type != SHT_NOBITS)
- return GetPtr(sections[section].sh_offset);
- else
- return nullptr;
- }
- bool IsCodeSection(int section) const {
- return sections[section].sh_type == SHT_PROGBITS;
- }
- const u8* GetSegmentPtr(int segment) {
- return GetPtr(segments[segment].p_offset);
- }
- u32 GetSectionAddr(SectionID section) const {
- return sectionAddrs[section];
- }
- unsigned int GetSectionSize(SectionID section) const {
- return sections[section].sh_size;
- }
- SectionID GetSectionByName(const char* name, int firstSection = 0) const; //-1 for not found
-
- bool DidRelocate() const {
- return relocate;
- }
-};
-
-ElfReader::ElfReader(void* ptr) {
- base = (char*)ptr;
- base32 = (u32*)ptr;
- header = (Elf32_Ehdr*)ptr;
-
- segments = (Elf32_Phdr*)(base + header->e_phoff);
- sections = (Elf32_Shdr*)(base + header->e_shoff);
-
- entryPoint = header->e_entry;
-}
-
-const char* ElfReader::GetSectionName(int section) const {
- if (sections[section].sh_type == SHT_NULL)
- return nullptr;
-
- int name_offset = sections[section].sh_name;
- const char* ptr = reinterpret_cast<const char*>(GetSectionDataPtr(header->e_shstrndx));
-
- if (ptr)
- return ptr + name_offset;
-
- return nullptr;
-}
-
-Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) {
- LOG_DEBUG(Loader, "String section: {}", header->e_shstrndx);
-
- // Should we relocate?
- relocate = (header->e_type != ET_EXEC);
-
- if (relocate) {
- LOG_DEBUG(Loader, "Relocatable module");
- entryPoint += vaddr;
- } else {
- LOG_DEBUG(Loader, "Prerelocated executable");
- }
- LOG_DEBUG(Loader, "{} segments:", header->e_phnum);
-
- // First pass : Get the bits into RAM
- const VAddr base_addr = relocate ? vaddr : 0;
-
- u64 total_image_size = 0;
- for (unsigned int i = 0; i < header->e_phnum; ++i) {
- const Elf32_Phdr* p = &segments[i];
- if (p->p_type == PT_LOAD) {
- total_image_size += (p->p_memsz + 0xFFF) & ~0xFFF;
- }
- }
-
- Kernel::PhysicalMemory program_image(total_image_size);
- std::size_t current_image_position = 0;
-
- Kernel::CodeSet codeset;
-
- for (unsigned int i = 0; i < header->e_phnum; ++i) {
- const Elf32_Phdr* p = &segments[i];
- LOG_DEBUG(Loader, "Type: {} Vaddr: {:08X} Filesz: {:08X} Memsz: {:08X} ", p->p_type,
- p->p_vaddr, p->p_filesz, p->p_memsz);
-
- if (p->p_type == PT_LOAD) {
- Kernel::CodeSet::Segment* codeset_segment;
- u32 permission_flags = p->p_flags & (PF_R | PF_W | PF_X);
- if (permission_flags == (PF_R | PF_X)) {
- codeset_segment = &codeset.CodeSegment();
- } else if (permission_flags == (PF_R)) {
- codeset_segment = &codeset.RODataSegment();
- } else if (permission_flags == (PF_R | PF_W)) {
- codeset_segment = &codeset.DataSegment();
- } else {
- LOG_ERROR(Loader, "Unexpected ELF PT_LOAD segment id {} with flags {:X}", i,
- p->p_flags);
- continue;
- }
-
- if (codeset_segment->size != 0) {
- LOG_ERROR(Loader,
- "ELF has more than one segment of the same type. Skipping extra "
- "segment (id {})",
- i);
- continue;
- }
-
- const VAddr segment_addr = base_addr + p->p_vaddr;
- const u32 aligned_size = (p->p_memsz + 0xFFF) & ~0xFFF;
-
- codeset_segment->offset = current_image_position;
- codeset_segment->addr = segment_addr;
- codeset_segment->size = aligned_size;
-
- std::memcpy(program_image.data() + current_image_position, GetSegmentPtr(i),
- p->p_filesz);
- current_image_position += aligned_size;
- }
- }
-
- codeset.entrypoint = base_addr + header->e_entry;
- codeset.memory = std::move(program_image);
-
- LOG_DEBUG(Loader, "Done loading.");
-
- return codeset;
-}
-
-SectionID ElfReader::GetSectionByName(const char* name, int firstSection) const {
- for (int i = firstSection; i < header->e_shnum; i++) {
- const char* secname = GetSectionName(i);
-
- if (secname != nullptr && strcmp(name, secname) == 0)
- return i;
- }
- return -1;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// Loader namespace
-
-namespace Loader {
-
-AppLoader_ELF::AppLoader_ELF(FileSys::VirtualFile file_) : AppLoader(std::move(file_)) {}
-
-FileType AppLoader_ELF::IdentifyType(const FileSys::VirtualFile& elf_file) {
- static constexpr u16 ELF_MACHINE_ARM{0x28};
-
- u32 magic = 0;
- if (4 != elf_file->ReadObject(&magic)) {
- return FileType::Error;
- }
-
- u16 machine = 0;
- if (2 != elf_file->ReadObject(&machine, 18)) {
- return FileType::Error;
- }
-
- if (Common::MakeMagic('\x7f', 'E', 'L', 'F') == magic && ELF_MACHINE_ARM == machine) {
- return FileType::ELF;
- }
-
- return FileType::Error;
-}
-
-AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::KProcess& process,
- [[maybe_unused]] Core::System& system) {
- if (is_loaded) {
- return {ResultStatus::ErrorAlreadyLoaded, {}};
- }
-
- std::vector<u8> buffer = file->ReadAllBytes();
- if (buffer.size() != file->GetSize()) {
- return {ResultStatus::ErrorIncorrectELFFileSize, {}};
- }
-
- const VAddr base_address = process.PageTable().GetCodeRegionStart();
- ElfReader elf_reader(&buffer[0]);
- Kernel::CodeSet codeset = elf_reader.LoadInto(base_address);
- const VAddr entry_point = codeset.entrypoint;
-
- // Setup the process code layout
- if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), buffer.size()).IsError()) {
- return {ResultStatus::ErrorNotInitialized, {}};
- }
-
- process.LoadModule(std::move(codeset), entry_point);
-
- is_loaded = true;
- return {ResultStatus::Success, LoadParameters{48, Core::Memory::DEFAULT_STACK_SIZE}};
-}
-
-} // namespace Loader
diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h
deleted file mode 100644
index bff51ec17..000000000
--- a/src/core/loader/elf.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "core/loader/loader.h"
-
-namespace Core {
-class System;
-}
-
-namespace Loader {
-
-/// Loads an ELF/AXF file
-class AppLoader_ELF final : public AppLoader {
-public:
- explicit AppLoader_ELF(FileSys::VirtualFile file);
-
- /**
- * Identifies whether or not the given file is an ELF file.
- *
- * @param elf_file The file to identify.
- *
- * @return FileType::ELF, or FileType::Error if the file is not an ELF file.
- */
- static FileType IdentifyType(const FileSys::VirtualFile& elf_file);
-
- FileType GetFileType() const override {
- return IdentifyType(file);
- }
-
- LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
-};
-
-} // namespace Loader
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
index 99ed34b00..d8a1bf82a 100644
--- a/src/core/loader/kip.cpp
+++ b/src/core/loader/kip.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "core/file_sys/kernel_executable.h"
@@ -15,7 +14,7 @@ namespace Loader {
namespace {
constexpr u32 PageAlignSize(u32 size) {
- return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK);
+ return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
}
} // Anonymous namespace
diff --git a/src/core/loader/kip.h b/src/core/loader/kip.h
index 5f914b4a8..63f66e85c 100644
--- a/src/core/loader/kip.h
+++ b/src/core/loader/kip.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 199e69e89..f24474ed8 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include <optional>
@@ -13,7 +12,6 @@
#include "core/core.h"
#include "core/hle/kernel/k_process.h"
#include "core/loader/deconstructed_rom_directory.h"
-#include "core/loader/elf.h"
#include "core/loader/kip.h"
#include "core/loader/nax.h"
#include "core/loader/nca.h"
@@ -40,8 +38,6 @@ std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
FileType IdentifyFile(FileSys::VirtualFile file) {
if (const auto romdir_type = IdentifyFileLoader<AppLoader_DeconstructedRomDirectory>(file)) {
return *romdir_type;
- } else if (const auto elf_type = IdentifyFileLoader<AppLoader_ELF>(file)) {
- return *elf_type;
} else if (const auto nso_type = IdentifyFileLoader<AppLoader_NSO>(file)) {
return *nso_type;
} else if (const auto nro_type = IdentifyFileLoader<AppLoader_NRO>(file)) {
@@ -70,8 +66,6 @@ FileType GuessFromFilename(const std::string& name) {
const std::string extension =
Common::ToLower(std::string(Common::FS::GetExtensionFromFilename(name)));
- if (extension == "elf")
- return FileType::ELF;
if (extension == "nro")
return FileType::NRO;
if (extension == "nso")
@@ -90,8 +84,6 @@ FileType GuessFromFilename(const std::string& name) {
std::string GetFileTypeString(FileType type) {
switch (type) {
- case FileType::ELF:
- return "ELF";
case FileType::NRO:
return "NRO";
case FileType::NSO:
@@ -209,10 +201,6 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V
FileType type, u64 program_id,
std::size_t program_index) {
switch (type) {
- // Standard ELF file format.
- case FileType::ELF:
- return std::make_unique<AppLoader_ELF>(std::move(file));
-
// NX NSO file format.
case FileType::NSO:
return std::make_unique<AppLoader_NSO>(std::move(file));
@@ -256,12 +244,17 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V
std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file,
u64 program_id, std::size_t program_index) {
+ if (!file) {
+ return nullptr;
+ }
+
FileType type = IdentifyFile(file);
const FileType filename_type = GuessFromFilename(file->GetName());
// Special case: 00 is either a NCA or NAX.
if (type != filename_type && !(file->GetName() == "00" && type == FileType::NAX)) {
- LOG_WARNING(Loader, "File {} has a different type than its extension.", file->GetName());
+ LOG_WARNING(Loader, "File {} has a different type ({}) than its extension.",
+ file->GetName(), GetFileTypeString(type));
if (FileType::Unknown == type) {
type = filename_type;
}
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 8b6b3b68f..7b43f70ed 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -35,7 +34,6 @@ namespace Loader {
enum class FileType {
Error,
Unknown,
- ELF,
NSO,
NRO,
NCA,
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp
index 3375dab7c..cf35b1249 100644
--- a/src/core/loader/nax.cpp
+++ b/src/core/loader/nax.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/content_archive.h"
#include "core/file_sys/romfs.h"
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h
index b3a50894f..d7f70db43 100644
--- a/src/core/loader/nax.h
+++ b/src/core/loader/nax.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index 219bbeaf5..513af194d 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <utility>
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index c0db8c740..d22d9146e 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 951ea966e..73d04d7ee 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <utility>
#include <vector>
@@ -126,7 +125,7 @@ FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& nro_file) {
}
static constexpr u32 PageAlignSize(u32 size) {
- return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK);
+ return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
}
static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data) {
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index fd453b402..ccb77b581 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 4a2224c02..4c3b3c655 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cinttypes>
#include <cstring>
@@ -46,7 +45,7 @@ std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
}
constexpr u32 PageAlignSize(u32 size) {
- return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK);
+ return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
}
} // Anonymous namespace
@@ -129,11 +128,10 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
// Apply patches if necessary
if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
- std::vector<u8> pi_header;
- pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header),
- reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader));
- pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.data(),
- program_image.data() + program_image.size());
+ std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
+ std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader));
+ std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(),
+ program_image.size());
pi_header = pm->PatchNSO(pi_header, nso_file.GetName());
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index f7b61bc2d..0b53b4ecd 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index f7ccc678d..80663e0e0 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <vector>
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index 378e4077a..003cc345c 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 8c6c1a3fd..c7b1b3815 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <vector>
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index 6e3810e48..2affb6c6e 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 88d6ec908..2ac792566 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
@@ -37,11 +36,11 @@ struct Memory::Impl {
}
void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target) {
- ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
- ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
- ASSERT_MSG(target >= DramMemoryMap::Base && target < DramMemoryMap::End,
- "Out of bounds target: {:016X}", target);
- MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory);
+ ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size);
+ ASSERT_MSG((base & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", base);
+ ASSERT_MSG(target >= DramMemoryMap::Base, "Out of bounds target: {:016X}", target);
+ MapPages(page_table, base / YUZU_PAGESIZE, size / YUZU_PAGESIZE, target,
+ Common::PageType::Memory);
if (Settings::IsFastmemEnabled()) {
system.DeviceMemory().buffer.Map(base, target - DramMemoryMap::Base, size);
@@ -49,9 +48,10 @@ struct Memory::Impl {
}
void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) {
- ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
- ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
- MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, 0, Common::PageType::Unmapped);
+ ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size);
+ ASSERT_MSG((base & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", base);
+ MapPages(page_table, base / YUZU_PAGESIZE, size / YUZU_PAGESIZE, 0,
+ Common::PageType::Unmapped);
if (Settings::IsFastmemEnabled()) {
system.DeviceMemory().buffer.Unmap(base, size);
@@ -59,7 +59,7 @@ struct Memory::Impl {
}
[[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(VAddr vaddr) const {
- const PAddr paddr{current_page_table->backing_addr[vaddr >> PAGE_BITS]};
+ const PAddr paddr{current_page_table->backing_addr[vaddr >> YUZU_PAGEBITS]};
if (!paddr) {
return {};
@@ -68,6 +68,16 @@ struct Memory::Impl {
return system.DeviceMemory().GetPointer(paddr) + vaddr;
}
+ [[nodiscard]] u8* GetPointerFromDebugMemory(VAddr vaddr) const {
+ const PAddr paddr{current_page_table->backing_addr[vaddr >> YUZU_PAGEBITS]};
+
+ if (paddr == 0) {
+ return {};
+ }
+
+ return system.DeviceMemory().GetPointer(paddr) + vaddr;
+ }
+
u8 Read8(const VAddr addr) {
return Read<u8>(addr);
}
@@ -168,13 +178,14 @@ struct Memory::Impl {
auto on_unmapped, auto on_memory, auto on_rasterizer, auto increment) {
const auto& page_table = process.PageTable().PageTableImpl();
std::size_t remaining_size = size;
- std::size_t page_index = addr >> PAGE_BITS;
- std::size_t page_offset = addr & PAGE_MASK;
+ std::size_t page_index = addr >> YUZU_PAGEBITS;
+ std::size_t page_offset = addr & YUZU_PAGEMASK;
while (remaining_size) {
const std::size_t copy_amount =
- std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
- const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
+ std::min(static_cast<std::size_t>(YUZU_PAGESIZE) - page_offset, remaining_size);
+ const auto current_vaddr =
+ static_cast<VAddr>((page_index << YUZU_PAGEBITS) + page_offset);
const auto [pointer, type] = page_table.pointers[page_index].PointerType();
switch (type) {
@@ -184,7 +195,13 @@ struct Memory::Impl {
}
case Common::PageType::Memory: {
DEBUG_ASSERT(pointer);
- u8* mem_ptr = pointer + page_offset + (page_index << PAGE_BITS);
+ u8* mem_ptr = pointer + page_offset + (page_index << YUZU_PAGEBITS);
+ on_memory(copy_amount, mem_ptr);
+ break;
+ }
+ case Common::PageType::DebugMemory: {
+ DEBUG_ASSERT(pointer);
+ u8* const mem_ptr{GetPointerFromDebugMemory(current_vaddr)};
on_memory(copy_amount, mem_ptr);
break;
}
@@ -317,6 +334,58 @@ struct Memory::Impl {
});
}
+ void MarkRegionDebug(VAddr vaddr, u64 size, bool debug) {
+ if (vaddr == 0) {
+ return;
+ }
+
+ // Iterate over a contiguous CPU address space, marking/unmarking the region.
+ // The region is at a granularity of CPU pages.
+
+ const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1;
+ for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) {
+ const Common::PageType page_type{
+ current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Type()};
+ if (debug) {
+ // Switch page type to debug if now debug
+ switch (page_type) {
+ case Common::PageType::Unmapped:
+ ASSERT_MSG(false, "Attempted to mark unmapped pages as debug");
+ break;
+ case Common::PageType::RasterizerCachedMemory:
+ case Common::PageType::DebugMemory:
+ // Page is already marked.
+ break;
+ case Common::PageType::Memory:
+ current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
+ nullptr, Common::PageType::DebugMemory);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ } else {
+ // Switch page type to non-debug if now non-debug
+ switch (page_type) {
+ case Common::PageType::Unmapped:
+ ASSERT_MSG(false, "Attempted to mark unmapped pages as non-debug");
+ break;
+ case Common::PageType::RasterizerCachedMemory:
+ case Common::PageType::Memory:
+ // Don't mess with already non-debug or rasterizer memory.
+ break;
+ case Common::PageType::DebugMemory: {
+ u8* const pointer{GetPointerFromDebugMemory(vaddr & ~YUZU_PAGEMASK)};
+ current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
+ pointer - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ }
+ }
+ }
+
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
if (vaddr == 0) {
return;
@@ -332,10 +401,10 @@ struct Memory::Impl {
// granularity of CPU pages, hence why we iterate on a CPU page basis (note: GPU page size
// is different). This assumes the specified GPU address region is contiguous as well.
- const u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1;
- for (u64 i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) {
+ const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1;
+ for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) {
const Common::PageType page_type{
- current_page_table->pointers[vaddr >> PAGE_BITS].Type()};
+ current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Type()};
if (cached) {
// Switch page type to cached if now cached
switch (page_type) {
@@ -343,8 +412,9 @@ struct Memory::Impl {
// It is not necessary for a process to have this region mapped into its address
// space, for example, a system module need not have a VRAM mapping.
break;
+ case Common::PageType::DebugMemory:
case Common::PageType::Memory:
- current_page_table->pointers[vaddr >> PAGE_BITS].Store(
+ current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
nullptr, Common::PageType::RasterizerCachedMemory);
break;
case Common::PageType::RasterizerCachedMemory:
@@ -361,21 +431,22 @@ struct Memory::Impl {
// It is not necessary for a process to have this region mapped into its address
// space, for example, a system module need not have a VRAM mapping.
break;
+ case Common::PageType::DebugMemory:
case Common::PageType::Memory:
// There can be more than one GPU region mapped per CPU region, so it's common
// that this area is already unmarked as cached.
break;
case Common::PageType::RasterizerCachedMemory: {
- u8* const pointer{GetPointerFromRasterizerCachedMemory(vaddr & ~PAGE_MASK)};
+ u8* const pointer{GetPointerFromRasterizerCachedMemory(vaddr & ~YUZU_PAGEMASK)};
if (pointer == nullptr) {
// It's possible that this function has been called while updating the
// pagetable after unmapping a VMA. In that case the underlying VMA will no
// longer exist, and we should just leave the pagetable entry blank.
- current_page_table->pointers[vaddr >> PAGE_BITS].Store(
+ current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
nullptr, Common::PageType::Unmapped);
} else {
- current_page_table->pointers[vaddr >> PAGE_BITS].Store(
- pointer - (vaddr & ~PAGE_MASK), Common::PageType::Memory);
+ current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
+ pointer - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory);
}
break;
}
@@ -397,8 +468,8 @@ struct Memory::Impl {
*/
void MapPages(Common::PageTable& page_table, VAddr base, u64 size, PAddr target,
Common::PageType type) {
- LOG_DEBUG(HW_Memory, "Mapping {:016X} onto {:016X}-{:016X}", target, base * PAGE_SIZE,
- (base + size) * PAGE_SIZE);
+ LOG_DEBUG(HW_Memory, "Mapping {:016X} onto {:016X}-{:016X}", target, base * YUZU_PAGESIZE,
+ (base + size) * YUZU_PAGESIZE);
// During boot, current_page_table might not be set yet, in which case we need not flush
if (system.IsPoweredOn()) {
@@ -406,7 +477,7 @@ struct Memory::Impl {
for (u64 i = 0; i < size; i++) {
const auto page = base + i;
if (page_table.pointers[page].Type() == Common::PageType::RasterizerCachedMemory) {
- gpu.FlushAndInvalidateRegion(page << PAGE_BITS, PAGE_SIZE);
+ gpu.FlushAndInvalidateRegion(page << YUZU_PAGEBITS, YUZU_PAGESIZE);
}
}
}
@@ -417,7 +488,7 @@ struct Memory::Impl {
if (!target) {
ASSERT_MSG(type != Common::PageType::Memory,
- "Mapping memory page without a pointer @ {:016x}", base * PAGE_SIZE);
+ "Mapping memory page without a pointer @ {:016x}", base * YUZU_PAGESIZE);
while (base != end) {
page_table.pointers[base].Store(nullptr, type);
@@ -428,21 +499,21 @@ struct Memory::Impl {
} else {
while (base != end) {
page_table.pointers[base].Store(
- system.DeviceMemory().GetPointer(target) - (base << PAGE_BITS), type);
- page_table.backing_addr[base] = target - (base << PAGE_BITS);
+ system.DeviceMemory().GetPointer(target) - (base << YUZU_PAGEBITS), type);
+ page_table.backing_addr[base] = target - (base << YUZU_PAGEBITS);
ASSERT_MSG(page_table.pointers[base].Pointer(),
"memory mapping base yield a nullptr within the table");
base += 1;
- target += PAGE_SIZE;
+ target += YUZU_PAGESIZE;
}
}
}
[[nodiscard]] u8* GetPointerImpl(VAddr vaddr, auto on_unmapped, auto on_rasterizer) const {
// AARCH64 masks the upper 16 bit of all memory accesses
- vaddr &= 0xffffffffffffLL;
+ vaddr &= 0xffffffffffffULL;
if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) {
on_unmapped();
@@ -450,7 +521,7 @@ struct Memory::Impl {
}
// Avoid adding any extra logic to this fast-path block
- const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw();
+ const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Raw();
if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
return &pointer[vaddr];
}
@@ -461,6 +532,8 @@ struct Memory::Impl {
case Common::PageType::Memory:
ASSERT_MSG(false, "Mapped memory page without a pointer @ 0x{:016X}", vaddr);
return nullptr;
+ case Common::PageType::DebugMemory:
+ return GetPointerFromDebugMemory(vaddr);
case Common::PageType::RasterizerCachedMemory: {
u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
on_rasterizer();
@@ -478,6 +551,11 @@ struct Memory::Impl {
[]() {});
}
+ [[nodiscard]] u8* GetPointerSilent(const VAddr vaddr) const {
+ return GetPointerImpl(
+ vaddr, []() {}, []() {});
+ }
+
/**
* Reads a particular data type out of memory at the given virtual address.
*
@@ -587,18 +665,36 @@ void Memory::UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) {
bool Memory::IsValidVirtualAddress(const VAddr vaddr) const {
const Kernel::KProcess& process = *system.CurrentProcess();
const auto& page_table = process.PageTable().PageTableImpl();
- const size_t page = vaddr >> PAGE_BITS;
+ const size_t page = vaddr >> YUZU_PAGEBITS;
if (page >= page_table.pointers.size()) {
return false;
}
const auto [pointer, type] = page_table.pointers[page].PointerType();
- return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory;
+ return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory ||
+ type == Common::PageType::DebugMemory;
+}
+
+bool Memory::IsValidVirtualAddressRange(VAddr base, u64 size) const {
+ VAddr end = base + size;
+ VAddr page = Common::AlignDown(base, YUZU_PAGESIZE);
+
+ for (; page < end; page += YUZU_PAGESIZE) {
+ if (!IsValidVirtualAddress(page)) {
+ return false;
+ }
+ }
+
+ return true;
}
u8* Memory::GetPointer(VAddr vaddr) {
return impl->GetPointer(vaddr);
}
+u8* Memory::GetPointerSilent(VAddr vaddr) {
+ return impl->GetPointerSilent(vaddr);
+}
+
const u8* Memory::GetPointer(VAddr vaddr) const {
return impl->GetPointer(vaddr);
}
@@ -691,8 +787,16 @@ void Memory::CopyBlock(const Kernel::KProcess& process, VAddr dest_addr, VAddr s
impl->CopyBlock(process, dest_addr, src_addr, size);
}
+void Memory::ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, const std::size_t size) {
+ impl->ZeroBlock(process, dest_addr, size);
+}
+
void Memory::RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
impl->RasterizerMarkRegionCached(vaddr, size, cached);
}
+void Memory::MarkRegionDebug(VAddr vaddr, u64 size, bool debug) {
+ impl->MarkRegionDebug(vaddr, size, debug);
+}
+
} // namespace Core::Memory
diff --git a/src/core/memory.h b/src/core/memory.h
index b5721b740..81eac448b 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -28,9 +27,9 @@ namespace Core::Memory {
* Page size used by the ARM architecture. This is the smallest granularity with which memory can
* be mapped.
*/
-constexpr std::size_t PAGE_BITS = 12;
-constexpr u64 PAGE_SIZE = 1ULL << PAGE_BITS;
-constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
+constexpr std::size_t YUZU_PAGEBITS = 12;
+constexpr u64 YUZU_PAGESIZE = 1ULL << YUZU_PAGEBITS;
+constexpr u64 YUZU_PAGEMASK = YUZU_PAGESIZE - 1;
/// Virtual user-space memory regions
enum : VAddr {
@@ -96,6 +95,17 @@ public:
[[nodiscard]] bool IsValidVirtualAddress(VAddr vaddr) const;
/**
+ * Checks whether or not the supplied range of addresses are all valid
+ * virtual addresses for the current process.
+ *
+ * @param base The address to begin checking.
+ * @param size The amount of bytes to check.
+ *
+ * @returns True if all bytes in the given range are valid, false otherwise.
+ */
+ [[nodiscard]] bool IsValidVirtualAddressRange(VAddr base, u64 size) const;
+
+ /**
* Gets a pointer to the given address.
*
* @param vaddr Virtual address to retrieve a pointer to.
@@ -104,6 +114,7 @@ public:
* If the address is not valid, nullptr will be returned.
*/
u8* GetPointer(VAddr vaddr);
+ u8* GetPointerSilent(VAddr vaddr);
template <typename T>
T* GetPointer(VAddr vaddr) {
@@ -426,6 +437,19 @@ public:
std::size_t size);
/**
+ * Zeros a range of bytes within the current process' address space at the specified
+ * virtual address.
+ *
+ * @param process The process that will have data zeroed within its address space.
+ * @param dest_addr The destination virtual address to zero the data from.
+ * @param size The size of the range to zero out, in bytes.
+ *
+ * @post The range [dest_addr, size) within the process' address space contains the
+ * value 0.
+ */
+ void ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size);
+
+ /**
* Marks each page within the specified address range as cached or uncached.
*
* @param vaddr The virtual address indicating the start of the address range.
@@ -435,6 +459,17 @@ public:
*/
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached);
+ /**
+ * Marks each page within the specified address range as debug or non-debug.
+ * Debug addresses are not accessible from fastmem pointers.
+ *
+ * @param vaddr The virtual address indicating the start of the address range.
+ * @param size The size of the address range in bytes.
+ * @param debug Whether or not any pages within the address range should be
+ * marked as debug or non-debug.
+ */
+ void MarkRegionDebug(VAddr vaddr, u64 size, bool debug);
+
private:
Core::System& system;
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index 12446c9ac..ffdbacc18 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <locale>
#include "common/hex_util.h"
@@ -185,10 +184,12 @@ CheatEngine::~CheatEngine() {
void CheatEngine::Initialize() {
event = Core::Timing::CreateEvent(
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
- [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ [this](std::uintptr_t user_data, s64 time,
+ std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
FrameCallback(user_data, ns_late);
+ return std::nullopt;
});
- core_timing.ScheduleEvent(CHEAT_ENGINE_NS, event);
+ core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, event);
metadata.process_id = system.CurrentProcess()->GetProcessID();
metadata.title_id = system.GetCurrentProcessProgramID();
@@ -238,8 +239,6 @@ void CheatEngine::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late
MICROPROFILE_SCOPE(Cheat_Engine);
vm.Execute(metadata);
-
- core_timing.ScheduleEvent(CHEAT_ENGINE_NS - ns_late, event);
}
} // namespace Core::Memory
diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h
index a8e041d9d..284abdd28 100644
--- a/src/core/memory/cheat_engine.h
+++ b/src/core/memory/cheat_engine.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/memory/dmnt_cheat_types.h b/src/core/memory/dmnt_cheat_types.h
index 5e60733dc..c6b40e505 100644
--- a/src/core/memory/dmnt_cheat_types.h
+++ b/src/core/memory/dmnt_cheat_types.h
@@ -1,26 +1,5 @@
-/*
- * Copyright (c) 2018-2019 Atmosphère-NX
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms and conditions of the GNU General Public License,
- * version 2, as published by the Free Software Foundation.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/*
- * Adapted by DarkLordZach for use/interaction with yuzu
- *
- * Modifications Copyright 2019 yuzu emulator team
- * Licensed under GPLv2 or any later version
- * Refer to the license.txt file included.
- */
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp
index dc04e37d2..de96fcb8e 100644
--- a/src/core/memory/dmnt_cheat_vm.cpp
+++ b/src/core/memory/dmnt_cheat_vm.cpp
@@ -1,26 +1,5 @@
-/*
- * Copyright (c) 2018-2019 Atmosphère-NX
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms and conditions of the GNU General Public License,
- * version 2, as published by the Free Software Foundation.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/*
- * Adapted by DarkLordZach for use/interaction with yuzu
- *
- * Modifications Copyright 2019 yuzu emulator team
- * Licensed under GPLv2 or any later version
- * Refer to the license.txt file included.
- */
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/scope_exit.h"
diff --git a/src/core/memory/dmnt_cheat_vm.h b/src/core/memory/dmnt_cheat_vm.h
index 707bee82b..641cb09c4 100644
--- a/src/core/memory/dmnt_cheat_vm.h
+++ b/src/core/memory/dmnt_cheat_vm.h
@@ -1,26 +1,5 @@
-/*
- * Copyright (c) 2018-2019 Atmosphère-NX
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms and conditions of the GNU General Public License,
- * version 2, as published by the Free Software Foundation.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/*
- * Adapted by DarkLordZach for use/interaction with yuzu
- *
- * Modifications Copyright 2019 yuzu emulator team
- * Licensed under GPLv2 or any later version
- * Refer to the license.txt file included.
- */
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/network/network.cpp b/src/core/network/network.cpp
deleted file mode 100644
index a3e0664b9..000000000
--- a/src/core/network/network.cpp
+++ /dev/null
@@ -1,634 +0,0 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <cstring>
-#include <limits>
-#include <utility>
-#include <vector>
-
-#include "common/error.h"
-
-#ifdef _WIN32
-#include <winsock2.h>
-#include <ws2tcpip.h>
-#elif YUZU_UNIX
-#include <arpa/inet.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <poll.h>
-#include <sys/socket.h>
-#include <unistd.h>
-#else
-#error "Unimplemented platform"
-#endif
-
-#include "common/assert.h"
-#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "common/settings.h"
-#include "core/network/network.h"
-#include "core/network/network_interface.h"
-#include "core/network/sockets.h"
-
-namespace Network {
-
-namespace {
-
-#ifdef _WIN32
-
-using socklen_t = int;
-
-void Initialize() {
- WSADATA wsa_data;
- (void)WSAStartup(MAKEWORD(2, 2), &wsa_data);
-}
-
-void Finalize() {
- WSACleanup();
-}
-
-sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
- sockaddr_in result;
-
-#if YUZU_UNIX
- result.sin_len = sizeof(result);
-#endif
-
- switch (static_cast<Domain>(input.family)) {
- case Domain::INET:
- result.sin_family = AF_INET;
- break;
- default:
- UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.family);
- result.sin_family = AF_INET;
- break;
- }
-
- result.sin_port = htons(input.portno);
-
- auto& ip = result.sin_addr.S_un.S_un_b;
- ip.s_b1 = input.ip[0];
- ip.s_b2 = input.ip[1];
- ip.s_b3 = input.ip[2];
- ip.s_b4 = input.ip[3];
-
- sockaddr addr;
- std::memcpy(&addr, &result, sizeof(addr));
- return addr;
-}
-
-LINGER MakeLinger(bool enable, u32 linger_value) {
- ASSERT(linger_value <= std::numeric_limits<u_short>::max());
-
- LINGER value;
- value.l_onoff = enable ? 1 : 0;
- value.l_linger = static_cast<u_short>(linger_value);
- return value;
-}
-
-bool EnableNonBlock(SOCKET fd, bool enable) {
- u_long value = enable ? 1 : 0;
- return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR;
-}
-
-Errno TranslateNativeError(int e) {
- switch (e) {
- case WSAEBADF:
- return Errno::BADF;
- case WSAEINVAL:
- return Errno::INVAL;
- case WSAEMFILE:
- return Errno::MFILE;
- case WSAENOTCONN:
- return Errno::NOTCONN;
- case WSAEWOULDBLOCK:
- return Errno::AGAIN;
- case WSAECONNREFUSED:
- return Errno::CONNREFUSED;
- case WSAEHOSTUNREACH:
- return Errno::HOSTUNREACH;
- case WSAENETDOWN:
- return Errno::NETDOWN;
- case WSAENETUNREACH:
- return Errno::NETUNREACH;
- default:
- return Errno::OTHER;
- }
-}
-
-#elif YUZU_UNIX // ^ _WIN32 v YUZU_UNIX
-
-using SOCKET = int;
-using WSAPOLLFD = pollfd;
-using ULONG = u64;
-
-constexpr SOCKET INVALID_SOCKET = -1;
-constexpr SOCKET SOCKET_ERROR = -1;
-
-constexpr int SD_RECEIVE = SHUT_RD;
-constexpr int SD_SEND = SHUT_WR;
-constexpr int SD_BOTH = SHUT_RDWR;
-
-void Initialize() {}
-
-void Finalize() {}
-
-sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
- sockaddr_in result;
-
- switch (static_cast<Domain>(input.family)) {
- case Domain::INET:
- result.sin_family = AF_INET;
- break;
- default:
- UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.family);
- result.sin_family = AF_INET;
- break;
- }
-
- result.sin_port = htons(input.portno);
-
- result.sin_addr.s_addr = input.ip[0] | input.ip[1] << 8 | input.ip[2] << 16 | input.ip[3] << 24;
-
- sockaddr addr;
- std::memcpy(&addr, &result, sizeof(addr));
- return addr;
-}
-
-int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) {
- return poll(fds, static_cast<nfds_t>(nfds), timeout);
-}
-
-int closesocket(SOCKET fd) {
- return close(fd);
-}
-
-linger MakeLinger(bool enable, u32 linger_value) {
- linger value;
- value.l_onoff = enable ? 1 : 0;
- value.l_linger = linger_value;
- return value;
-}
-
-bool EnableNonBlock(int fd, bool enable) {
- int flags = fcntl(fd, F_GETFL);
- if (flags == -1) {
- return false;
- }
- if (enable) {
- flags |= O_NONBLOCK;
- } else {
- flags &= ~O_NONBLOCK;
- }
- return fcntl(fd, F_SETFL, flags) == 0;
-}
-
-Errno TranslateNativeError(int e) {
- switch (e) {
- case EBADF:
- return Errno::BADF;
- case EINVAL:
- return Errno::INVAL;
- case EMFILE:
- return Errno::MFILE;
- case ENOTCONN:
- return Errno::NOTCONN;
- case EAGAIN:
- return Errno::AGAIN;
- case ECONNREFUSED:
- return Errno::CONNREFUSED;
- case EHOSTUNREACH:
- return Errno::HOSTUNREACH;
- case ENETDOWN:
- return Errno::NETDOWN;
- case ENETUNREACH:
- return Errno::NETUNREACH;
- default:
- return Errno::OTHER;
- }
-}
-
-#endif
-
-Errno GetAndLogLastError() {
-#ifdef _WIN32
- int e = WSAGetLastError();
-#else
- int e = errno;
-#endif
- const Errno err = TranslateNativeError(e);
- if (err == Errno::AGAIN) {
- return err;
- }
- LOG_ERROR(Network, "Socket operation error: {}", Common::NativeErrorToString(e));
- return err;
-}
-
-int TranslateDomain(Domain domain) {
- switch (domain) {
- case Domain::INET:
- return AF_INET;
- default:
- UNIMPLEMENTED_MSG("Unimplemented domain={}", domain);
- return 0;
- }
-}
-
-int TranslateType(Type type) {
- switch (type) {
- case Type::STREAM:
- return SOCK_STREAM;
- case Type::DGRAM:
- return SOCK_DGRAM;
- default:
- UNIMPLEMENTED_MSG("Unimplemented type={}", type);
- return 0;
- }
-}
-
-int TranslateProtocol(Protocol protocol) {
- switch (protocol) {
- case Protocol::TCP:
- return IPPROTO_TCP;
- case Protocol::UDP:
- return IPPROTO_UDP;
- default:
- UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol);
- return 0;
- }
-}
-
-SockAddrIn TranslateToSockAddrIn(sockaddr input_) {
- sockaddr_in input;
- std::memcpy(&input, &input_, sizeof(input));
-
- SockAddrIn result;
-
- switch (input.sin_family) {
- case AF_INET:
- result.family = Domain::INET;
- break;
- default:
- UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.sin_family);
- result.family = Domain::INET;
- break;
- }
-
- result.portno = ntohs(input.sin_port);
-
- result.ip = TranslateIPv4(input.sin_addr);
-
- return result;
-}
-
-short TranslatePollEvents(PollEvents events) {
- short result = 0;
-
- if (True(events & PollEvents::In)) {
- events &= ~PollEvents::In;
- result |= POLLIN;
- }
- if (True(events & PollEvents::Pri)) {
- events &= ~PollEvents::Pri;
-#ifdef _WIN32
- LOG_WARNING(Service, "Winsock doesn't support POLLPRI");
-#else
- result |= POLLPRI;
-#endif
- }
- if (True(events & PollEvents::Out)) {
- events &= ~PollEvents::Out;
- result |= POLLOUT;
- }
-
- UNIMPLEMENTED_IF_MSG((u16)events != 0, "Unhandled guest events=0x{:x}", (u16)events);
-
- return result;
-}
-
-PollEvents TranslatePollRevents(short revents) {
- PollEvents result{};
- const auto translate = [&result, &revents](short host, PollEvents guest) {
- if ((revents & host) != 0) {
- revents &= static_cast<short>(~host);
- result |= guest;
- }
- };
-
- translate(POLLIN, PollEvents::In);
- translate(POLLPRI, PollEvents::Pri);
- translate(POLLOUT, PollEvents::Out);
- translate(POLLERR, PollEvents::Err);
- translate(POLLHUP, PollEvents::Hup);
-
- UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents=0x{:x}", revents);
-
- return result;
-}
-
-template <typename T>
-Errno SetSockOpt(SOCKET fd, int option, T value) {
- const int result =
- setsockopt(fd, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value));
- if (result != SOCKET_ERROR) {
- return Errno::SUCCESS;
- }
- return GetAndLogLastError();
-}
-
-} // Anonymous namespace
-
-NetworkInstance::NetworkInstance() {
- Initialize();
-}
-
-NetworkInstance::~NetworkInstance() {
- Finalize();
-}
-
-std::optional<IPv4Address> GetHostIPv4Address() {
- const std::string& selected_network_interface = Settings::values.network_interface.GetValue();
- const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
- if (network_interfaces.size() == 0) {
- LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces");
- return {};
- }
-
- const auto res =
- std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) {
- return iface.name == selected_network_interface;
- });
-
- if (res != network_interfaces.end()) {
- char ip_addr[16] = {};
- ASSERT(inet_ntop(AF_INET, &res->ip_address, ip_addr, sizeof(ip_addr)) != nullptr);
- return TranslateIPv4(res->ip_address);
- } else {
- LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface);
- return {};
- }
-}
-
-std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
- const size_t num = pollfds.size();
-
- std::vector<WSAPOLLFD> host_pollfds(pollfds.size());
- std::transform(pollfds.begin(), pollfds.end(), host_pollfds.begin(), [](PollFD fd) {
- WSAPOLLFD result;
- result.fd = fd.socket->fd;
- result.events = TranslatePollEvents(fd.events);
- result.revents = 0;
- return result;
- });
-
- const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout);
- if (result == 0) {
- ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(),
- [](WSAPOLLFD fd) { return fd.revents == 0; }));
- return {0, Errno::SUCCESS};
- }
-
- for (size_t i = 0; i < num; ++i) {
- pollfds[i].revents = TranslatePollRevents(host_pollfds[i].revents);
- }
-
- if (result > 0) {
- return {result, Errno::SUCCESS};
- }
-
- ASSERT(result == SOCKET_ERROR);
-
- return {-1, GetAndLogLastError()};
-}
-
-Socket::~Socket() {
- if (fd == INVALID_SOCKET) {
- return;
- }
- (void)closesocket(fd);
- fd = INVALID_SOCKET;
-}
-
-Socket::Socket(Socket&& rhs) noexcept : fd{std::exchange(rhs.fd, INVALID_SOCKET)} {}
-
-Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
- fd = socket(TranslateDomain(domain), TranslateType(type), TranslateProtocol(protocol));
- if (fd != INVALID_SOCKET) {
- return Errno::SUCCESS;
- }
-
- return GetAndLogLastError();
-}
-
-std::pair<Socket::AcceptResult, Errno> Socket::Accept() {
- sockaddr addr;
- socklen_t addrlen = sizeof(addr);
- const SOCKET new_socket = accept(fd, &addr, &addrlen);
-
- if (new_socket == INVALID_SOCKET) {
- return {AcceptResult{}, GetAndLogLastError()};
- }
-
- AcceptResult result;
- result.socket = std::make_unique<Socket>();
- result.socket->fd = new_socket;
-
- ASSERT(addrlen == sizeof(sockaddr_in));
- result.sockaddr_in = TranslateToSockAddrIn(addr);
-
- return {std::move(result), Errno::SUCCESS};
-}
-
-Errno Socket::Connect(SockAddrIn addr_in) {
- const sockaddr host_addr_in = TranslateFromSockAddrIn(addr_in);
- if (connect(fd, &host_addr_in, sizeof(host_addr_in)) != SOCKET_ERROR) {
- return Errno::SUCCESS;
- }
-
- return GetAndLogLastError();
-}
-
-std::pair<SockAddrIn, Errno> Socket::GetPeerName() {
- sockaddr addr;
- socklen_t addrlen = sizeof(addr);
- if (getpeername(fd, &addr, &addrlen) == SOCKET_ERROR) {
- return {SockAddrIn{}, GetAndLogLastError()};
- }
-
- ASSERT(addrlen == sizeof(sockaddr_in));
- return {TranslateToSockAddrIn(addr), Errno::SUCCESS};
-}
-
-std::pair<SockAddrIn, Errno> Socket::GetSockName() {
- sockaddr addr;
- socklen_t addrlen = sizeof(addr);
- if (getsockname(fd, &addr, &addrlen) == SOCKET_ERROR) {
- return {SockAddrIn{}, GetAndLogLastError()};
- }
-
- ASSERT(addrlen == sizeof(sockaddr_in));
- return {TranslateToSockAddrIn(addr), Errno::SUCCESS};
-}
-
-Errno Socket::Bind(SockAddrIn addr) {
- const sockaddr addr_in = TranslateFromSockAddrIn(addr);
- if (bind(fd, &addr_in, sizeof(addr_in)) != SOCKET_ERROR) {
- return Errno::SUCCESS;
- }
-
- return GetAndLogLastError();
-}
-
-Errno Socket::Listen(s32 backlog) {
- if (listen(fd, backlog) != SOCKET_ERROR) {
- return Errno::SUCCESS;
- }
-
- return GetAndLogLastError();
-}
-
-Errno Socket::Shutdown(ShutdownHow how) {
- int host_how = 0;
- switch (how) {
- case ShutdownHow::RD:
- host_how = SD_RECEIVE;
- break;
- case ShutdownHow::WR:
- host_how = SD_SEND;
- break;
- case ShutdownHow::RDWR:
- host_how = SD_BOTH;
- break;
- default:
- UNIMPLEMENTED_MSG("Unimplemented flag how={}", how);
- return Errno::SUCCESS;
- }
- if (shutdown(fd, host_how) != SOCKET_ERROR) {
- return Errno::SUCCESS;
- }
-
- return GetAndLogLastError();
-}
-
-std::pair<s32, Errno> Socket::Recv(int flags, std::vector<u8>& message) {
- ASSERT(flags == 0);
- ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
-
- const auto result =
- recv(fd, reinterpret_cast<char*>(message.data()), static_cast<int>(message.size()), 0);
- if (result != SOCKET_ERROR) {
- return {static_cast<s32>(result), Errno::SUCCESS};
- }
-
- return {-1, GetAndLogLastError()};
-}
-
-std::pair<s32, Errno> Socket::RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) {
- ASSERT(flags == 0);
- ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
-
- sockaddr addr_in{};
- socklen_t addrlen = sizeof(addr_in);
- socklen_t* const p_addrlen = addr ? &addrlen : nullptr;
- sockaddr* const p_addr_in = addr ? &addr_in : nullptr;
-
- const auto result = recvfrom(fd, reinterpret_cast<char*>(message.data()),
- static_cast<int>(message.size()), 0, p_addr_in, p_addrlen);
- if (result != SOCKET_ERROR) {
- if (addr) {
- ASSERT(addrlen == sizeof(addr_in));
- *addr = TranslateToSockAddrIn(addr_in);
- }
- return {static_cast<s32>(result), Errno::SUCCESS};
- }
-
- return {-1, GetAndLogLastError()};
-}
-
-std::pair<s32, Errno> Socket::Send(const std::vector<u8>& message, int flags) {
- ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
- ASSERT(flags == 0);
-
- const auto result = send(fd, reinterpret_cast<const char*>(message.data()),
- static_cast<int>(message.size()), 0);
- if (result != SOCKET_ERROR) {
- return {static_cast<s32>(result), Errno::SUCCESS};
- }
-
- return {-1, GetAndLogLastError()};
-}
-
-std::pair<s32, Errno> Socket::SendTo(u32 flags, const std::vector<u8>& message,
- const SockAddrIn* addr) {
- ASSERT(flags == 0);
-
- const sockaddr* to = nullptr;
- const int tolen = addr ? sizeof(sockaddr) : 0;
- sockaddr host_addr_in;
-
- if (addr) {
- host_addr_in = TranslateFromSockAddrIn(*addr);
- to = &host_addr_in;
- }
-
- const auto result = sendto(fd, reinterpret_cast<const char*>(message.data()),
- static_cast<int>(message.size()), 0, to, tolen);
- if (result != SOCKET_ERROR) {
- return {static_cast<s32>(result), Errno::SUCCESS};
- }
-
- return {-1, GetAndLogLastError()};
-}
-
-Errno Socket::Close() {
- [[maybe_unused]] const int result = closesocket(fd);
- ASSERT(result == 0);
- fd = INVALID_SOCKET;
-
- return Errno::SUCCESS;
-}
-
-Errno Socket::SetLinger(bool enable, u32 linger) {
- return SetSockOpt(fd, SO_LINGER, MakeLinger(enable, linger));
-}
-
-Errno Socket::SetReuseAddr(bool enable) {
- return SetSockOpt<u32>(fd, SO_REUSEADDR, enable ? 1 : 0);
-}
-
-Errno Socket::SetBroadcast(bool enable) {
- return SetSockOpt<u32>(fd, SO_BROADCAST, enable ? 1 : 0);
-}
-
-Errno Socket::SetSndBuf(u32 value) {
- return SetSockOpt(fd, SO_SNDBUF, value);
-}
-
-Errno Socket::SetRcvBuf(u32 value) {
- return SetSockOpt(fd, SO_RCVBUF, value);
-}
-
-Errno Socket::SetSndTimeo(u32 value) {
- return SetSockOpt(fd, SO_SNDTIMEO, value);
-}
-
-Errno Socket::SetRcvTimeo(u32 value) {
- return SetSockOpt(fd, SO_RCVTIMEO, value);
-}
-
-Errno Socket::SetNonBlock(bool enable) {
- if (EnableNonBlock(fd, enable)) {
- return Errno::SUCCESS;
- }
- return GetAndLogLastError();
-}
-
-bool Socket::IsOpened() const {
- return fd != INVALID_SOCKET;
-}
-
-} // namespace Network
diff --git a/src/core/network/network.h b/src/core/network/network.h
deleted file mode 100644
index e85df3ab7..000000000
--- a/src/core/network/network.h
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <optional>
-
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-
-#ifdef _WIN32
-#include <winsock2.h>
-#elif YUZU_UNIX
-#include <netinet/in.h>
-#endif
-
-namespace Network {
-
-class Socket;
-
-/// Error code for network functions
-enum class Errno {
- SUCCESS,
- BADF,
- INVAL,
- MFILE,
- NOTCONN,
- AGAIN,
- CONNREFUSED,
- HOSTUNREACH,
- NETDOWN,
- NETUNREACH,
- OTHER,
-};
-
-/// Address families
-enum class Domain {
- INET, ///< Address family for IPv4
-};
-
-/// Socket types
-enum class Type {
- STREAM,
- DGRAM,
- RAW,
- SEQPACKET,
-};
-
-/// Protocol values for sockets
-enum class Protocol {
- ICMP,
- TCP,
- UDP,
-};
-
-/// Shutdown mode
-enum class ShutdownHow {
- RD,
- WR,
- RDWR,
-};
-
-/// Array of IPv4 address
-using IPv4Address = std::array<u8, 4>;
-
-/// Cross-platform sockaddr structure
-struct SockAddrIn {
- Domain family;
- IPv4Address ip;
- u16 portno;
-};
-
-/// Cross-platform poll fd structure
-
-enum class PollEvents : u16 {
- // Using Pascal case because IN is a macro on Windows.
- In = 1 << 0,
- Pri = 1 << 1,
- Out = 1 << 2,
- Err = 1 << 3,
- Hup = 1 << 4,
- Nval = 1 << 5,
-};
-
-DECLARE_ENUM_FLAG_OPERATORS(PollEvents);
-
-struct PollFD {
- Socket* socket;
- PollEvents events;
- PollEvents revents;
-};
-
-class NetworkInstance {
-public:
- explicit NetworkInstance();
- ~NetworkInstance();
-};
-
-#ifdef _WIN32
-constexpr IPv4Address TranslateIPv4(in_addr addr) {
- auto& bytes = addr.S_un.S_un_b;
- return IPv4Address{bytes.s_b1, bytes.s_b2, bytes.s_b3, bytes.s_b4};
-}
-#elif YUZU_UNIX
-constexpr IPv4Address TranslateIPv4(in_addr addr) {
- const u32 bytes = addr.s_addr;
- return IPv4Address{static_cast<u8>(bytes), static_cast<u8>(bytes >> 8),
- static_cast<u8>(bytes >> 16), static_cast<u8>(bytes >> 24)};
-}
-#endif
-
-/// @brief Returns host's IPv4 address
-/// @return human ordered IPv4 address (e.g. 192.168.0.1) as an array
-std::optional<IPv4Address> GetHostIPv4Address();
-
-} // namespace Network
diff --git a/src/core/network/network_interface.cpp b/src/core/network/network_interface.cpp
deleted file mode 100644
index 6811f21b1..000000000
--- a/src/core/network/network_interface.cpp
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <fstream>
-#include <sstream>
-#include <vector>
-
-#include "common/bit_cast.h"
-#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "common/settings.h"
-#include "common/string_util.h"
-#include "core/network/network_interface.h"
-
-#ifdef _WIN32
-#include <iphlpapi.h>
-#else
-#include <cerrno>
-#include <ifaddrs.h>
-#include <net/if.h>
-#endif
-
-namespace Network {
-
-#ifdef _WIN32
-
-std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
- std::vector<IP_ADAPTER_ADDRESSES> adapter_addresses;
- DWORD ret = ERROR_BUFFER_OVERFLOW;
- DWORD buf_size = 0;
-
- // retry up to 5 times
- for (int i = 0; i < 5 && ret == ERROR_BUFFER_OVERFLOW; i++) {
- ret = GetAdaptersAddresses(
- AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS,
- nullptr, adapter_addresses.data(), &buf_size);
-
- if (ret != ERROR_BUFFER_OVERFLOW) {
- break;
- }
-
- adapter_addresses.resize((buf_size / sizeof(IP_ADAPTER_ADDRESSES)) + 1);
- }
-
- if (ret != NO_ERROR) {
- LOG_ERROR(Network, "Failed to get network interfaces with GetAdaptersAddresses");
- return {};
- }
-
- std::vector<NetworkInterface> result;
-
- for (auto current_address = adapter_addresses.data(); current_address != nullptr;
- current_address = current_address->Next) {
- if (current_address->FirstUnicastAddress == nullptr ||
- current_address->FirstUnicastAddress->Address.lpSockaddr == nullptr) {
- continue;
- }
-
- if (current_address->OperStatus != IfOperStatusUp) {
- continue;
- }
-
- const auto ip_addr = Common::BitCast<struct sockaddr_in>(
- *current_address->FirstUnicastAddress->Address.lpSockaddr)
- .sin_addr;
-
- ULONG mask = 0;
- if (ConvertLengthToIpv4Mask(current_address->FirstUnicastAddress->OnLinkPrefixLength,
- &mask) != NO_ERROR) {
- LOG_ERROR(Network, "Failed to convert IPv4 prefix length to subnet mask");
- continue;
- }
-
- struct in_addr gateway = {.S_un{.S_addr{0}}};
- if (current_address->FirstGatewayAddress != nullptr &&
- current_address->FirstGatewayAddress->Address.lpSockaddr != nullptr) {
- gateway = Common::BitCast<struct sockaddr_in>(
- *current_address->FirstGatewayAddress->Address.lpSockaddr)
- .sin_addr;
- }
-
- result.emplace_back(NetworkInterface{
- .name{Common::UTF16ToUTF8(std::wstring{current_address->FriendlyName})},
- .ip_address{ip_addr},
- .subnet_mask = in_addr{.S_un{.S_addr{mask}}},
- .gateway = gateway});
- }
-
- return result;
-}
-
-#else
-
-std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
- struct ifaddrs* ifaddr = nullptr;
-
- if (getifaddrs(&ifaddr) != 0) {
- LOG_ERROR(Network, "Failed to get network interfaces with getifaddrs: {}",
- std::strerror(errno));
- return {};
- }
-
- std::vector<NetworkInterface> result;
-
- for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
- if (ifa->ifa_addr == nullptr || ifa->ifa_netmask == nullptr) {
- continue;
- }
-
- if (ifa->ifa_addr->sa_family != AF_INET) {
- continue;
- }
-
- if ((ifa->ifa_flags & IFF_UP) == 0 || (ifa->ifa_flags & IFF_LOOPBACK) != 0) {
- continue;
- }
-
- u32 gateway{};
-
- std::ifstream file{"/proc/net/route"};
- if (!file.is_open()) {
- LOG_ERROR(Network, "Failed to open \"/proc/net/route\"");
-
- result.emplace_back(NetworkInterface{
- .name{ifa->ifa_name},
- .ip_address{Common::BitCast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr},
- .subnet_mask{Common::BitCast<struct sockaddr_in>(*ifa->ifa_netmask).sin_addr},
- .gateway{in_addr{.s_addr = gateway}}});
- continue;
- }
-
- // ignore header
- file.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
-
- bool gateway_found = false;
-
- for (std::string line; std::getline(file, line);) {
- std::istringstream iss{line};
-
- std::string iface_name;
- iss >> iface_name;
- if (iface_name != ifa->ifa_name) {
- continue;
- }
-
- iss >> std::hex;
-
- u32 dest{};
- iss >> dest;
- if (dest != 0) {
- // not the default route
- continue;
- }
-
- iss >> gateway;
-
- u16 flags{};
- iss >> flags;
-
- // flag RTF_GATEWAY (defined in <linux/route.h>)
- if ((flags & 0x2) == 0) {
- continue;
- }
-
- gateway_found = true;
- break;
- }
-
- if (!gateway_found) {
- gateway = 0;
- }
-
- result.emplace_back(NetworkInterface{
- .name{ifa->ifa_name},
- .ip_address{Common::BitCast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr},
- .subnet_mask{Common::BitCast<struct sockaddr_in>(*ifa->ifa_netmask).sin_addr},
- .gateway{in_addr{.s_addr = gateway}}});
- }
-
- freeifaddrs(ifaddr);
-
- return result;
-}
-
-#endif
-
-std::optional<NetworkInterface> GetSelectedNetworkInterface() {
- const auto& selected_network_interface = Settings::values.network_interface.GetValue();
- const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
- if (network_interfaces.size() == 0) {
- LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces");
- return std::nullopt;
- }
-
- const auto res =
- std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) {
- return iface.name == selected_network_interface;
- });
-
- if (res == network_interfaces.end()) {
- LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface);
- return std::nullopt;
- }
-
- return *res;
-}
-
-} // namespace Network
diff --git a/src/core/network/network_interface.h b/src/core/network/network_interface.h
deleted file mode 100644
index 980edb2f5..000000000
--- a/src/core/network/network_interface.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <optional>
-#include <string>
-#include <vector>
-
-#ifdef _WIN32
-#include <winsock2.h>
-#else
-#include <netinet/in.h>
-#endif
-
-namespace Network {
-
-struct NetworkInterface {
- std::string name;
- struct in_addr ip_address;
- struct in_addr subnet_mask;
- struct in_addr gateway;
-};
-
-std::vector<NetworkInterface> GetAvailableNetworkInterfaces();
-std::optional<NetworkInterface> GetSelectedNetworkInterface();
-
-} // namespace Network
diff --git a/src/core/network/sockets.h b/src/core/network/sockets.h
deleted file mode 100644
index a44393325..000000000
--- a/src/core/network/sockets.h
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2020 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <utility>
-
-#if defined(_WIN32)
-#include <winsock.h>
-#elif !YUZU_UNIX
-#error "Platform not implemented"
-#endif
-
-#include "common/common_types.h"
-#include "core/network/network.h"
-
-// TODO: C++20 Replace std::vector usages with std::span
-
-namespace Network {
-
-class Socket {
-public:
- struct AcceptResult {
- std::unique_ptr<Socket> socket;
- SockAddrIn sockaddr_in;
- };
-
- explicit Socket() = default;
- ~Socket();
-
- Socket(const Socket&) = delete;
- Socket& operator=(const Socket&) = delete;
-
- Socket(Socket&& rhs) noexcept;
-
- // Avoid closing sockets implicitly
- Socket& operator=(Socket&&) noexcept = delete;
-
- Errno Initialize(Domain domain, Type type, Protocol protocol);
-
- Errno Close();
-
- std::pair<AcceptResult, Errno> Accept();
-
- Errno Connect(SockAddrIn addr_in);
-
- std::pair<SockAddrIn, Errno> GetPeerName();
-
- std::pair<SockAddrIn, Errno> GetSockName();
-
- Errno Bind(SockAddrIn addr);
-
- Errno Listen(s32 backlog);
-
- Errno Shutdown(ShutdownHow how);
-
- std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message);
-
- std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr);
-
- std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags);
-
- std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, const SockAddrIn* addr);
-
- Errno SetLinger(bool enable, u32 linger);
-
- Errno SetReuseAddr(bool enable);
-
- Errno SetBroadcast(bool enable);
-
- Errno SetSndBuf(u32 value);
-
- Errno SetRcvBuf(u32 value);
-
- Errno SetSndTimeo(u32 value);
-
- Errno SetRcvTimeo(u32 value);
-
- Errno SetNonBlock(bool enable);
-
- bool IsOpened() const;
-
-#if defined(_WIN32)
- SOCKET fd = INVALID_SOCKET;
-#elif YUZU_UNIX
- int fd = -1;
-#endif
-};
-
-std::pair<s32, Errno> Poll(std::vector<PollFD>& poll_fds, s32 timeout);
-
-} // namespace Network
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index 52c43c857..f09c176f8 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <chrono>
@@ -53,13 +52,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 +78,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 +90,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 +119,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/perf_stats.h b/src/core/perf_stats.h
index 816202588..dd6becc02 100644
--- a/src/core/perf_stats.h
+++ b/src/core/perf_stats.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index d4becdc0a..6e21296f6 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <ctime>
#include <fstream>
@@ -8,7 +7,6 @@
#include <fmt/chrono.h>
#include <fmt/format.h>
-#include <fmt/ostream.h>
#include <nlohmann/json.hpp>
#include "common/fs/file.h"
@@ -65,7 +63,7 @@ json GetYuzuVersionData() {
};
}
-json GetReportCommonData(u64 title_id, ResultCode result, const std::string& timestamp,
+json GetReportCommonData(u64 title_id, Result result, const std::string& timestamp,
std::optional<u128> user_id = {}) {
auto out = json{
{"title_id", fmt::format("{:016X}", title_id)},
@@ -200,8 +198,8 @@ Reporter::Reporter(System& system_) : system(system_) {
Reporter::~Reporter() = default;
-void Reporter::SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point,
- u64 sp, u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
+void Reporter::SaveCrashReport(u64 title_id, Result result, u64 set_flags, u64 entry_point, u64 sp,
+ u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
const std::array<u64, 31>& registers,
const std::array<u64, 32>& backtrace, u32 backtrace_size,
const std::string& arch, u32 unk10) const {
@@ -340,7 +338,7 @@ void Reporter::SavePlayReport(PlayReportType type, u64 title_id, std::vector<std
SaveToFile(std::move(out), GetPath("play_report", title_id, timestamp));
}
-void Reporter::SaveErrorReport(u64 title_id, ResultCode result,
+void Reporter::SaveErrorReport(u64 title_id, Result result,
std::optional<std::string> custom_text_main,
std::optional<std::string> custom_text_detail) const {
if (!IsReportingEnabled()) {
diff --git a/src/core/reporter.h b/src/core/reporter.h
index 6e9edeea3..68755cbde 100644
--- a/src/core/reporter.h
+++ b/src/core/reporter.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,7 +9,7 @@
#include <vector>
#include "common/common_types.h"
-union ResultCode;
+union Result;
namespace Kernel {
class HLERequestContext;
@@ -30,7 +29,7 @@ public:
~Reporter();
// Used by fatal services
- void SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point, u64 sp,
+ void SaveCrashReport(u64 title_id, Result result, u64 set_flags, u64 entry_point, u64 sp,
u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
const std::array<u64, 31>& registers, const std::array<u64, 32>& backtrace,
u32 backtrace_size, const std::string& arch, u32 unk10) const;
@@ -61,7 +60,7 @@ public:
std::optional<u64> process_id = {}, std::optional<u128> user_id = {}) const;
// Used by error applet
- void SaveErrorReport(u64 title_id, ResultCode result,
+ void SaveErrorReport(u64 title_id, Result result,
std::optional<std::string> custom_text_main = {},
std::optional<std::string> custom_text_detail = {}) const;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 654db0b52..abcf6eb11 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index 6f3d45bea..887dc98f3 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp
index 032c71aff..98ebbbf32 100644
--- a/src/core/tools/freezer.cpp
+++ b/src/core/tools/freezer.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/logging/log.h"
@@ -26,7 +25,6 @@ u64 MemoryReadWidth(Core::Memory::Memory& memory, u32 width, VAddr addr) {
return memory.Read64(addr);
default:
UNREACHABLE();
- return 0;
}
}
@@ -55,8 +53,10 @@ Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& m
: core_timing{core_timing_}, memory{memory_} {
event = Core::Timing::CreateEvent(
"MemoryFreezer::FrameCallback",
- [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ [this](std::uintptr_t user_data, s64 time,
+ std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
FrameCallback(user_data, ns_late);
+ return std::nullopt;
});
core_timing.ScheduleEvent(memory_freezer_ns, event);
}
@@ -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/core/tools/freezer.h b/src/core/tools/freezer.h
index 067134e93..0d6df5217 100644
--- a/src/core/tools/freezer.h
+++ b/src/core/tools/freezer.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/dedicated_room/CMakeLists.txt b/src/dedicated_room/CMakeLists.txt
new file mode 100644
index 000000000..2d9731f19
--- /dev/null
+++ b/src/dedicated_room/CMakeLists.txt
@@ -0,0 +1,27 @@
+# SPDX-FileCopyrightText: 2017 Citra Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
+
+add_executable(yuzu-room
+ yuzu_room.cpp
+ yuzu_room.rc
+)
+
+create_target_directory_groups(yuzu-room)
+
+target_link_libraries(yuzu-room PRIVATE common network)
+if (ENABLE_WEB_SERVICE)
+ target_compile_definitions(yuzu-room PRIVATE -DENABLE_WEB_SERVICE)
+ target_link_libraries(yuzu-room PRIVATE web_service)
+endif()
+
+target_link_libraries(yuzu-room PRIVATE mbedtls mbedcrypto)
+if (MSVC)
+ target_link_libraries(yuzu-room PRIVATE getopt)
+endif()
+target_link_libraries(yuzu-room PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
+
+if(UNIX AND NOT APPLE)
+ install(TARGETS yuzu-room)
+endif()
diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp
new file mode 100644
index 000000000..359891883
--- /dev/null
+++ b/src/dedicated_room/yuzu_room.cpp
@@ -0,0 +1,393 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <regex>
+#include <string>
+#include <thread>
+
+#ifdef _WIN32
+// windows.h needs to be included before shellapi.h
+#include <windows.h>
+
+#include <shellapi.h>
+#endif
+
+#include <mbedtls/base64.h>
+#include "common/common_types.h"
+#include "common/detached_tasks.h"
+#include "common/fs/file.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/logging/backend.h"
+#include "common/logging/log.h"
+#include "common/scm_rev.h"
+#include "common/settings.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "network/announce_multiplayer_session.h"
+#include "network/network.h"
+#include "network/room.h"
+#include "network/verify_user.h"
+
+#ifdef ENABLE_WEB_SERVICE
+#include "web_service/verify_user_jwt.h"
+#endif
+
+#undef _UNICODE
+#include <getopt.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#endif
+
+static void PrintHelp(const char* argv0) {
+ LOG_INFO(Network,
+ "Usage: {}"
+ " [options] <filename>\n"
+ "--room-name The name of the room\n"
+ "--room-description The room description\n"
+ "--port The port used for the room\n"
+ "--max_members The maximum number of players for this room\n"
+ "--password The password for the room\n"
+ "--preferred-game The preferred game for this room\n"
+ "--preferred-game-id The preferred game-id for this room\n"
+ "--username The username used for announce\n"
+ "--token The token used for announce\n"
+ "--web-api-url yuzu Web API url\n"
+ "--ban-list-file The file for storing the room ban list\n"
+ "--log-file The file for storing the room log\n"
+ "--enable-yuzu-mods Allow yuzu Community Moderators to moderate on your room\n"
+ "-h, --help Display this help and exit\n"
+ "-v, --version Output version information and exit\n",
+ argv0);
+}
+
+static void PrintVersion() {
+ LOG_INFO(Network, "yuzu dedicated room {} {} Libnetwork: {}", Common::g_scm_branch,
+ Common::g_scm_desc, Network::network_version);
+}
+
+/// The magic text at the beginning of a yuzu-room ban list file.
+static constexpr char BanListMagic[] = "YuzuRoom-BanList-1";
+
+static constexpr char token_delimiter{':'};
+
+static void PadToken(std::string& token) {
+ std::size_t outlen = 0;
+
+ std::array<unsigned char, 512> output{};
+ std::array<unsigned char, 2048> roundtrip{};
+ for (size_t i = 0; i < 3; i++) {
+ mbedtls_base64_decode(output.data(), output.size(), &outlen,
+ reinterpret_cast<const unsigned char*>(token.c_str()),
+ token.length());
+ mbedtls_base64_encode(roundtrip.data(), roundtrip.size(), &outlen, output.data(), outlen);
+ if (memcmp(roundtrip.data(), token.data(), token.size()) == 0) {
+ break;
+ }
+ token.push_back('=');
+ }
+}
+
+static std::string UsernameFromDisplayToken(const std::string& display_token) {
+ std::size_t outlen;
+
+ std::array<unsigned char, 512> output{};
+ mbedtls_base64_decode(output.data(), output.size(), &outlen,
+ reinterpret_cast<const unsigned char*>(display_token.c_str()),
+ display_token.length());
+ std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen);
+ return decoded_display_token.substr(0, decoded_display_token.find(token_delimiter));
+}
+
+static std::string TokenFromDisplayToken(const std::string& display_token) {
+ std::size_t outlen;
+
+ std::array<unsigned char, 512> output{};
+ mbedtls_base64_decode(output.data(), output.size(), &outlen,
+ reinterpret_cast<const unsigned char*>(display_token.c_str()),
+ display_token.length());
+ std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen);
+ return decoded_display_token.substr(decoded_display_token.find(token_delimiter) + 1);
+}
+
+static Network::Room::BanList LoadBanList(const std::string& path) {
+ std::ifstream file;
+ Common::FS::OpenFileStream(file, path, std::ios_base::in);
+ if (!file || file.eof()) {
+ LOG_ERROR(Network, "Could not open ban list!");
+ return {};
+ }
+ std::string magic;
+ std::getline(file, magic);
+ if (magic != BanListMagic) {
+ LOG_ERROR(Network, "Ban list is not valid!");
+ return {};
+ }
+
+ // false = username ban list, true = ip ban list
+ bool ban_list_type = false;
+ Network::Room::UsernameBanList username_ban_list;
+ Network::Room::IPBanList ip_ban_list;
+ while (!file.eof()) {
+ std::string line;
+ std::getline(file, line);
+ line.erase(std::remove(line.begin(), line.end(), '\0'), line.end());
+ line = Common::StripSpaces(line);
+ if (line.empty()) {
+ // An empty line marks start of the IP ban list
+ ban_list_type = true;
+ continue;
+ }
+ if (ban_list_type) {
+ ip_ban_list.emplace_back(line);
+ } else {
+ username_ban_list.emplace_back(line);
+ }
+ }
+
+ return {username_ban_list, ip_ban_list};
+}
+
+static void SaveBanList(const Network::Room::BanList& ban_list, const std::string& path) {
+ std::ofstream file;
+ Common::FS::OpenFileStream(file, path, std::ios_base::out);
+ if (!file) {
+ LOG_ERROR(Network, "Could not save ban list!");
+ return;
+ }
+
+ file << BanListMagic << "\n";
+
+ // Username ban list
+ for (const auto& username : ban_list.first) {
+ file << username << "\n";
+ }
+ file << "\n";
+
+ // IP ban list
+ for (const auto& ip : ban_list.second) {
+ file << ip << "\n";
+ }
+}
+
+static void InitializeLogging(const std::string& log_file) {
+ Common::Log::Initialize();
+ Common::Log::SetColorConsoleBackendEnabled(true);
+ Common::Log::Start();
+}
+
+/// Application entry point
+int main(int argc, char** argv) {
+ Common::DetachedTasks detached_tasks;
+ int option_index = 0;
+ char* endarg;
+
+ std::string room_name;
+ std::string room_description;
+ std::string password;
+ std::string preferred_game;
+ std::string username;
+ std::string token;
+ std::string web_api_url;
+ std::string ban_list_file;
+ std::string log_file = "yuzu-room.log";
+ u64 preferred_game_id = 0;
+ u32 port = Network::DefaultRoomPort;
+ u32 max_members = 16;
+ bool enable_yuzu_mods = false;
+
+ static struct option long_options[] = {
+ {"room-name", required_argument, 0, 'n'},
+ {"room-description", required_argument, 0, 'd'},
+ {"port", required_argument, 0, 'p'},
+ {"max_members", required_argument, 0, 'm'},
+ {"password", required_argument, 0, 'w'},
+ {"preferred-game", required_argument, 0, 'g'},
+ {"preferred-game-id", required_argument, 0, 'i'},
+ {"username", optional_argument, 0, 'u'},
+ {"token", required_argument, 0, 't'},
+ {"web-api-url", required_argument, 0, 'a'},
+ {"ban-list-file", required_argument, 0, 'b'},
+ {"log-file", required_argument, 0, 'l'},
+ {"enable-yuzu-mods", no_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'v'},
+ {0, 0, 0, 0},
+ };
+
+ InitializeLogging(log_file);
+
+ while (optind < argc) {
+ int arg = getopt_long(argc, argv, "n:d:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index);
+ if (arg != -1) {
+ switch (static_cast<char>(arg)) {
+ case 'n':
+ room_name.assign(optarg);
+ break;
+ case 'd':
+ room_description.assign(optarg);
+ break;
+ case 'p':
+ port = strtoul(optarg, &endarg, 0);
+ break;
+ case 'm':
+ max_members = strtoul(optarg, &endarg, 0);
+ break;
+ case 'w':
+ password.assign(optarg);
+ break;
+ case 'g':
+ preferred_game.assign(optarg);
+ break;
+ case 'i':
+ preferred_game_id = strtoull(optarg, &endarg, 16);
+ break;
+ case 'u':
+ username.assign(optarg);
+ break;
+ case 't':
+ token.assign(optarg);
+ break;
+ case 'a':
+ web_api_url.assign(optarg);
+ break;
+ case 'b':
+ ban_list_file.assign(optarg);
+ break;
+ case 'l':
+ log_file.assign(optarg);
+ break;
+ case 'e':
+ enable_yuzu_mods = true;
+ break;
+ case 'h':
+ PrintHelp(argv[0]);
+ return 0;
+ case 'v':
+ PrintVersion();
+ return 0;
+ }
+ }
+ }
+
+ if (room_name.empty()) {
+ LOG_ERROR(Network, "Room name is empty!");
+ PrintHelp(argv[0]);
+ return -1;
+ }
+ if (preferred_game.empty()) {
+ LOG_ERROR(Network, "Preferred game is empty!");
+ PrintHelp(argv[0]);
+ return -1;
+ }
+ if (preferred_game_id == 0) {
+ LOG_ERROR(Network,
+ "preferred-game-id not set!\nThis should get set to allow users to find your "
+ "room.\nSet with --preferred-game-id id");
+ }
+ if (max_members > Network::MaxConcurrentConnections || max_members < 2) {
+ LOG_ERROR(Network, "max_members needs to be in the range 2 - {}!",
+ Network::MaxConcurrentConnections);
+ PrintHelp(argv[0]);
+ return -1;
+ }
+ if (port > UINT16_MAX) {
+ LOG_ERROR(Network, "Port needs to be in the range 0 - 65535!");
+ PrintHelp(argv[0]);
+ return -1;
+ }
+ if (ban_list_file.empty()) {
+ LOG_ERROR(Network, "Ban list file not set!\nThis should get set to load and save room ban "
+ "list.\nSet with --ban-list-file <file>");
+ }
+ bool announce = true;
+ if (token.empty() && announce) {
+ announce = false;
+ LOG_INFO(Network, "Token is empty: Hosting a private room");
+ }
+ if (web_api_url.empty() && announce) {
+ announce = false;
+ LOG_INFO(Network, "Endpoint url is empty: Hosting a private room");
+ }
+ if (announce) {
+ if (username.empty()) {
+ LOG_INFO(Network, "Hosting a public room");
+ Settings::values.web_api_url = web_api_url;
+ PadToken(token);
+ Settings::values.yuzu_username = UsernameFromDisplayToken(token);
+ username = Settings::values.yuzu_username.GetValue();
+ Settings::values.yuzu_token = TokenFromDisplayToken(token);
+ } else {
+ LOG_INFO(Network, "Hosting a public room");
+ Settings::values.web_api_url = web_api_url;
+ Settings::values.yuzu_username = username;
+ Settings::values.yuzu_token = token;
+ }
+ }
+ if (!announce && enable_yuzu_mods) {
+ enable_yuzu_mods = false;
+ LOG_INFO(Network, "Can not enable yuzu Moderators for private rooms");
+ }
+
+ // Load the ban list
+ Network::Room::BanList ban_list;
+ if (!ban_list_file.empty()) {
+ ban_list = LoadBanList(ban_list_file);
+ }
+
+ std::unique_ptr<Network::VerifyUser::Backend> verify_backend;
+ if (announce) {
+#ifdef ENABLE_WEB_SERVICE
+ verify_backend =
+ std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url.GetValue());
+#else
+ LOG_INFO(Network,
+ "yuzu Web Services is not available with this build: validation is disabled.");
+ verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
+#endif
+ } else {
+ verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
+ }
+
+ Network::RoomNetwork network{};
+ network.Init();
+ if (auto room = network.GetRoom().lock()) {
+ AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game,
+ .id = preferred_game_id};
+ if (!room->Create(room_name, room_description, "", port, password, max_members, username,
+ preferred_game_info, std::move(verify_backend), ban_list,
+ enable_yuzu_mods)) {
+ LOG_INFO(Network, "Failed to create room: ");
+ return -1;
+ }
+ LOG_INFO(Network, "Room is open. Close with Q+Enter...");
+ auto announce_session = std::make_unique<Core::AnnounceMultiplayerSession>(network);
+ if (announce) {
+ announce_session->Start();
+ }
+ while (room->GetState() == Network::Room::State::Open) {
+ std::string in;
+ std::cin >> in;
+ if (in.size() > 0) {
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+ if (announce) {
+ announce_session->Stop();
+ }
+ announce_session.reset();
+ // Save the ban list
+ if (!ban_list_file.empty()) {
+ SaveBanList(room->GetBanList(), ban_list_file);
+ }
+ room->Destroy();
+ }
+ network.Shutdown();
+ detached_tasks.WaitForAllTasks();
+ return 0;
+}
diff --git a/src/dedicated_room/yuzu_room.rc b/src/dedicated_room/yuzu_room.rc
new file mode 100644
index 000000000..a08957684
--- /dev/null
+++ b/src/dedicated_room/yuzu_room.rc
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "winresrc.h"
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+YUZU_ICON ICON "../../dist/yuzu.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RT_MANIFEST
+//
+
+0 RT_MANIFEST "../../dist/yuzu.manifest"
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index d4fa69a77..2cf9eb97f 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -1,4 +1,9 @@
+# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
add_library(input_common STATIC
+ drivers/camera.cpp
+ drivers/camera.h
drivers/gc_adapter.cpp
drivers/gc_adapter.h
drivers/keyboard.cpp
@@ -13,6 +18,8 @@ add_library(input_common STATIC
drivers/touch_screen.h
drivers/udp_client.cpp
drivers/udp_client.h
+ drivers/virtual_amiibo.cpp
+ drivers/virtual_amiibo.h
helpers/stick_from_buttons.cpp
helpers/stick_from_buttons.h
helpers/touch_from_buttons.cpp
@@ -44,7 +51,6 @@ else()
-Werror
-Werror=conversion
-Werror=ignored-qualifiers
- -Werror=shadow
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
-Werror=unused-variable
diff --git a/src/input_common/drivers/camera.cpp b/src/input_common/drivers/camera.cpp
new file mode 100644
index 000000000..dceea67e0
--- /dev/null
+++ b/src/input_common/drivers/camera.cpp
@@ -0,0 +1,82 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <fmt/format.h>
+
+#include "common/param_package.h"
+#include "input_common/drivers/camera.h"
+
+namespace InputCommon {
+constexpr PadIdentifier identifier = {
+ .guid = Common::UUID{},
+ .port = 0,
+ .pad = 0,
+};
+
+Camera::Camera(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
+ PreSetController(identifier);
+}
+
+void Camera::SetCameraData(std::size_t width, std::size_t height, std::vector<u32> data) {
+ const std::size_t desired_width = getImageWidth();
+ const std::size_t desired_height = getImageHeight();
+ status.data.resize(desired_width * desired_height);
+
+ // Resize image to desired format
+ for (std::size_t y = 0; y < desired_height; y++) {
+ for (std::size_t x = 0; x < desired_width; x++) {
+ const std::size_t pixel_index = y * desired_width + x;
+ const std::size_t old_x = width * x / desired_width;
+ const std::size_t old_y = height * y / desired_height;
+ const std::size_t data_pixel_index = old_y * width + old_x;
+ status.data[pixel_index] = static_cast<u8>(data[data_pixel_index] & 0xFF);
+ }
+ }
+
+ SetCamera(identifier, status);
+}
+
+std::size_t Camera::getImageWidth() const {
+ switch (status.format) {
+ case Common::Input::CameraFormat::Size320x240:
+ return 320;
+ case Common::Input::CameraFormat::Size160x120:
+ return 160;
+ case Common::Input::CameraFormat::Size80x60:
+ return 80;
+ case Common::Input::CameraFormat::Size40x30:
+ return 40;
+ case Common::Input::CameraFormat::Size20x15:
+ return 20;
+ case Common::Input::CameraFormat::None:
+ default:
+ return 0;
+ }
+}
+
+std::size_t Camera::getImageHeight() const {
+ switch (status.format) {
+ case Common::Input::CameraFormat::Size320x240:
+ return 240;
+ case Common::Input::CameraFormat::Size160x120:
+ return 120;
+ case Common::Input::CameraFormat::Size80x60:
+ return 60;
+ case Common::Input::CameraFormat::Size40x30:
+ return 30;
+ case Common::Input::CameraFormat::Size20x15:
+ return 15;
+ case Common::Input::CameraFormat::None:
+ default:
+ return 0;
+ }
+}
+
+Common::Input::CameraError Camera::SetCameraFormat(
+ [[maybe_unused]] const PadIdentifier& identifier_,
+ const Common::Input::CameraFormat camera_format) {
+ status.format = camera_format;
+ return Common::Input::CameraError::None;
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/drivers/camera.h b/src/input_common/drivers/camera.h
new file mode 100644
index 000000000..b8a7c75e5
--- /dev/null
+++ b/src/input_common/drivers/camera.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "input_common/input_engine.h"
+
+namespace InputCommon {
+
+/**
+ * A button device factory representing a keyboard. It receives keyboard events and forward them
+ * to all button devices it created.
+ */
+class Camera final : public InputEngine {
+public:
+ explicit Camera(std::string input_engine_);
+
+ void SetCameraData(std::size_t width, std::size_t height, std::vector<u32> data);
+
+ std::size_t getImageWidth() const;
+ std::size_t getImageHeight() const;
+
+ Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_,
+ Common::Input::CameraFormat camera_format) override;
+
+ Common::Input::CameraStatus status{};
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp
index 155caae42..f4dd24e7d 100644
--- a/src/input_common/drivers/gc_adapter.cpp
+++ b/src/input_common/drivers/gc_adapter.cpp
@@ -1,6 +1,5 @@
-// Copyright 2014 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/format.h>
#include <libusb.h>
@@ -91,7 +90,7 @@ GCAdapter::~GCAdapter() {
void GCAdapter::AdapterInputThread(std::stop_token stop_token) {
LOG_DEBUG(Input, "Input thread started");
- Common::SetCurrentThreadName("yuzu:input:GCAdapter");
+ Common::SetCurrentThreadName("GCAdapter");
s32 payload_size{};
AdapterPayload adapter_payload{};
@@ -215,7 +214,7 @@ void GCAdapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_
}
void GCAdapter::AdapterScanThread(std::stop_token stop_token) {
- Common::SetCurrentThreadName("yuzu:input:ScanGCAdapter");
+ Common::SetCurrentThreadName("ScanGCAdapter");
usb_adapter_handle = nullptr;
pads = {};
while (!stop_token.stop_requested() && !Setup()) {
@@ -524,4 +523,20 @@ Common::Input::ButtonNames GCAdapter::GetUIName(const Common::ParamPackage& para
return Common::Input::ButtonNames::Invalid;
}
+bool GCAdapter::IsStickInverted(const Common::ParamPackage& params) {
+ if (!params.Has("port")) {
+ return false;
+ }
+
+ const auto x_axis = static_cast<PadAxes>(params.Get("axis_x", 0));
+ const auto y_axis = static_cast<PadAxes>(params.Get("axis_y", 0));
+ if (x_axis != PadAxes::StickY && x_axis != PadAxes::SubstickY) {
+ return false;
+ }
+ if (y_axis != PadAxes::StickX && y_axis != PadAxes::SubstickX) {
+ return false;
+ }
+ return true;
+}
+
} // namespace InputCommon
diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h
index 7ce1912a3..8682da847 100644
--- a/src/input_common/drivers/gc_adapter.h
+++ b/src/input_common/drivers/gc_adapter.h
@@ -1,12 +1,10 @@
-// Copyright 2014 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
-#include <mutex>
#include <stop_token>
#include <string>
#include <thread>
@@ -36,6 +34,8 @@ public:
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
+ bool IsStickInverted(const Common::ParamPackage& params) override;
+
private:
enum class PadButton {
Undefined = 0x0000,
diff --git a/src/input_common/drivers/keyboard.cpp b/src/input_common/drivers/keyboard.cpp
index 59e3d9cc0..71e612fbf 100644
--- a/src/input_common/drivers/keyboard.cpp
+++ b/src/input_common/drivers/keyboard.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/param_package.h"
#include "common/settings_input.h"
diff --git a/src/input_common/drivers/keyboard.h b/src/input_common/drivers/keyboard.h
index 3856c882c..62436dba4 100644
--- a/src/input_common/drivers/keyboard.h
+++ b/src/input_common/drivers/keyboard.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp
index 3c9a4e747..98c3157a8 100644
--- a/src/input_common/drivers/mouse.cpp
+++ b/src/input_common/drivers/mouse.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <stop_token>
#include <thread>
@@ -38,7 +37,7 @@ Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_))
}
void Mouse::UpdateThread(std::stop_token stop_token) {
- Common::SetCurrentThreadName("yuzu:input:Mouse");
+ Common::SetCurrentThreadName("Mouse");
constexpr int update_time = 10;
while (!stop_token.stop_requested()) {
if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h
index c5833b8ed..286ce1cf6 100644
--- a/src/input_common/drivers/mouse.h
+++ b/src/input_common/drivers/mouse.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index 5cf1987ad..b72e4b397 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "common/math_util.h"
@@ -13,11 +12,11 @@
namespace InputCommon {
namespace {
-std::string GetGUID(SDL_Joystick* joystick) {
+Common::UUID GetGUID(SDL_Joystick* joystick) {
const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
- char guid_str[33];
- SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
- return guid_str;
+ std::array<u8, 16> data{};
+ std::memcpy(data.data(), guid.data, sizeof(data));
+ return Common::UUID{data};
}
} // Anonymous namespace
@@ -31,9 +30,9 @@ static int SDLEventWatcher(void* user_data, SDL_Event* event) {
class SDLJoystick {
public:
- SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
+ SDLJoystick(Common::UUID guid_, int port_, SDL_Joystick* joystick,
SDL_GameController* game_controller)
- : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
+ : guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
sdl_controller{game_controller, &SDL_GameControllerClose} {
EnableMotion();
}
@@ -41,13 +40,13 @@ public:
void EnableMotion() {
if (sdl_controller) {
SDL_GameController* controller = sdl_controller.get();
- if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) {
+ has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL);
+ has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO);
+ if (has_accel) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
- has_accel = true;
}
- if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) {
+ if (has_gyro) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
- has_gyro = true;
}
}
}
@@ -62,7 +61,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) {
@@ -120,7 +119,7 @@ public:
*/
const PadIdentifier GetPadIdentifier() const {
return {
- .guid = Common::UUID{guid},
+ .guid = guid,
.port = static_cast<std::size_t>(port),
.pad = 0,
};
@@ -129,7 +128,7 @@ public:
/**
* The guid of the joystick
*/
- const std::string& GetGUID() const {
+ const Common::UUID& GetGUID() const {
return guid;
}
@@ -175,22 +174,23 @@ public:
return false;
}
- BatteryLevel GetBatteryLevel() {
+ Common::Input::BatteryLevel GetBatteryLevel() {
const auto level = SDL_JoystickCurrentPowerLevel(sdl_joystick.get());
switch (level) {
case SDL_JOYSTICK_POWER_EMPTY:
- return BatteryLevel::Empty;
+ return Common::Input::BatteryLevel::Empty;
case SDL_JOYSTICK_POWER_LOW:
- return BatteryLevel::Low;
+ return Common::Input::BatteryLevel::Low;
case SDL_JOYSTICK_POWER_MEDIUM:
- return BatteryLevel::Medium;
+ return Common::Input::BatteryLevel::Medium;
case SDL_JOYSTICK_POWER_FULL:
case SDL_JOYSTICK_POWER_MAX:
- return BatteryLevel::Full;
- case SDL_JOYSTICK_POWER_UNKNOWN:
+ return Common::Input::BatteryLevel::Full;
case SDL_JOYSTICK_POWER_WIRED:
+ return Common::Input::BatteryLevel::Charging;
+ case SDL_JOYSTICK_POWER_UNKNOWN:
default:
- return BatteryLevel::Charging;
+ return Common::Input::BatteryLevel::None;
}
}
@@ -227,7 +227,7 @@ public:
}
private:
- std::string guid;
+ Common::UUID guid;
int port;
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
@@ -239,8 +239,8 @@ private:
BasicMotion motion;
};
-std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) {
- std::lock_guard lock{joystick_map_mutex};
+std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const Common::UUID& guid, int port) {
+ std::scoped_lock lock{joystick_map_mutex};
const auto it = joystick_map.find(guid);
if (it != joystick_map.end()) {
@@ -258,11 +258,15 @@ std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string&
return joystick_map[guid].emplace_back(std::move(joystick));
}
+std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) {
+ return GetSDLJoystickByGUID(Common::UUID{guid}, port);
+}
+
std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
- const std::string guid = GetGUID(sdl_joystick);
+ const auto 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()) {
@@ -294,13 +298,14 @@ void SDLDriver::InitJoystick(int joystick_index) {
return;
}
- const std::string guid = GetGUID(sdl_joystick);
+ const auto 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());
SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
+ joystick->EnableMotion();
joystick_map[guid].emplace_back(std::move(joystick));
return;
}
@@ -312,6 +317,7 @@ void SDLDriver::InitJoystick(int joystick_index) {
if (joystick_it != joystick_guid_list.end()) {
(*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
+ (*joystick_it)->EnableMotion();
return;
}
@@ -319,13 +325,14 @@ void SDLDriver::InitJoystick(int joystick_index) {
auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
PreSetController(joystick->GetPadIdentifier());
SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
+ joystick->EnableMotion();
joystick_guid_list.emplace_back(std::move(joystick));
}
void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) {
- const std::string guid = GetGUID(sdl_joystick);
+ const auto 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(),
@@ -351,6 +358,8 @@ void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetButton(identifier, event.jbutton.button, true);
+ // Battery doesn't trigger an event so just update every button press
+ SetBattery(identifier, joystick->GetBatteryLevel());
}
break;
}
@@ -389,7 +398,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();
}
@@ -427,13 +436,21 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
initialized = true;
if (start_thread) {
poll_thread = std::thread([this] {
- Common::SetCurrentThreadName("yuzu:input:SDL");
+ Common::SetCurrentThreadName("SDL_MainLoop");
using namespace std::chrono_literals;
while (initialized) {
SDL_PumpEvents();
std::this_thread::sleep_for(1ms);
}
});
+ vibration_thread = std::thread([this] {
+ Common::SetCurrentThreadName("SDL_Vibration");
+ using namespace std::chrono_literals;
+ while (initialized) {
+ SendVibrations();
+ std::this_thread::sleep_for(10ms);
+ }
+ });
}
// Because the events for joystick connection happens before we have our event watcher added, we
// can just open all the joysticks right here
@@ -449,6 +466,7 @@ SDLDriver::~SDLDriver() {
initialized = false;
if (start_thread) {
poll_thread.join();
+ vibration_thread.join();
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
}
}
@@ -466,7 +484,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
devices.emplace_back(Common::ParamPackage{
{"engine", GetEngineName()},
{"display", std::move(name)},
- {"guid", joystick->GetGUID()},
+ {"guid", joystick->GetGUID().RawString()},
{"port", std::to_string(joystick->GetPort())},
});
if (joystick->IsJoyconLeft()) {
@@ -489,8 +507,8 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
devices.emplace_back(Common::ParamPackage{
{"engine", GetEngineName()},
{"display", std::move(name)},
- {"guid", joystick->GetGUID()},
- {"guid2", joystick2->GetGUID()},
+ {"guid", joystick->GetGUID().RawString()},
+ {"guid2", joystick2->GetGUID().RawString()},
{"port", std::to_string(joystick->GetPort())},
});
}
@@ -528,57 +546,75 @@ Common::Input::VibrationError SDLDriver::SetRumble(
.type = Common::Input::VibrationAmplificationType::Exponential,
};
- if (!joystick->RumblePlay(new_vibration)) {
- return Common::Input::VibrationError::Unknown;
+ if (vibration.type == Common::Input::VibrationAmplificationType::Test) {
+ if (!joystick->RumblePlay(new_vibration)) {
+ return Common::Input::VibrationError::Unknown;
+ }
+ return Common::Input::VibrationError::None;
}
+ vibration_queue.Push(VibrationRequest{
+ .identifier = identifier,
+ .vibration = new_vibration,
+ });
+
return Common::Input::VibrationError::None;
}
-Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid,
+void SDLDriver::SendVibrations() {
+ while (!vibration_queue.Empty()) {
+ VibrationRequest request;
+ vibration_queue.Pop(request);
+ const auto joystick = GetSDLJoystickByGUID(request.identifier.guid.RawString(),
+ static_cast<int>(request.identifier.port));
+ joystick->RumblePlay(request.vibration);
+ }
+}
+
+Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, const Common::UUID& guid,
s32 axis, float value) const {
Common::ParamPackage params{};
params.Set("engine", GetEngineName());
params.Set("port", port);
- params.Set("guid", std::move(guid));
+ params.Set("guid", guid.RawString());
params.Set("axis", axis);
params.Set("threshold", "0.5");
params.Set("invert", value < 0 ? "-" : "+");
return params;
}
-Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, std::string guid,
+Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, const Common::UUID& guid,
s32 button) const {
Common::ParamPackage params{};
params.Set("engine", GetEngineName());
params.Set("port", port);
- params.Set("guid", std::move(guid));
+ params.Set("guid", guid.RawString());
params.Set("button", button);
return params;
}
-Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, std::string guid, s32 hat,
- u8 value) const {
+Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, const Common::UUID& guid,
+ s32 hat, u8 value) const {
Common::ParamPackage params{};
params.Set("engine", GetEngineName());
params.Set("port", port);
- params.Set("guid", std::move(guid));
+ params.Set("guid", guid.RawString());
params.Set("hat", hat);
params.Set("direction", GetHatButtonName(value));
return params;
}
-Common::ParamPackage SDLDriver::BuildMotionParam(int port, std::string guid) const {
+Common::ParamPackage SDLDriver::BuildMotionParam(int port, const Common::UUID& guid) const {
Common::ParamPackage params{};
params.Set("engine", GetEngineName());
params.Set("motion", 0);
params.Set("port", port);
- params.Set("guid", std::move(guid));
+ params.Set("guid", guid.RawString());
return params;
}
Common::ParamPackage SDLDriver::BuildParamPackageForBinding(
- int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const {
+ int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const {
switch (binding.bindType) {
case SDL_CONTROLLER_BINDTYPE_NONE:
break;
@@ -931,4 +967,37 @@ u8 SDLDriver::GetHatButtonId(const std::string& direction_name) const {
return direction;
}
+bool SDLDriver::IsStickInverted(const Common::ParamPackage& params) {
+ if (!params.Has("guid") || !params.Has("port")) {
+ return false;
+ }
+ const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
+ if (joystick == nullptr) {
+ return false;
+ }
+ auto* controller = joystick->GetSDLGameController();
+ if (controller == nullptr) {
+ return false;
+ }
+
+ const auto& axis_x = params.Get("axis_x", 0);
+ const auto& axis_y = params.Get("axis_y", 0);
+ const auto& binding_left_x =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
+ const auto& binding_right_x =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
+ const auto& binding_left_y =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
+ const auto& binding_right_y =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
+
+ if (axis_x != binding_left_y.value.axis && axis_x != binding_right_y.value.axis) {
+ return false;
+ }
+ if (axis_y != binding_left_x.value.axis && axis_y != binding_right_x.value.axis) {
+ return false;
+ }
+ return true;
+}
+
} // namespace InputCommon
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h
index 4cde3606f..fc3a44572 100644
--- a/src/input_common/drivers/sdl_driver.h
+++ b/src/input_common/drivers/sdl_driver.h
@@ -1,6 +1,5 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -12,6 +11,7 @@
#include <SDL.h>
#include "common/common_types.h"
+#include "common/threadsafe_queue.h"
#include "input_common/input_engine.h"
union SDL_Event;
@@ -46,6 +46,7 @@ public:
* Check how many identical joysticks (by guid) were connected before the one with sdl_id and so
* tie it to a SDLJoystick with the same guid and that port
*/
+ std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const Common::UUID& guid, int port);
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
std::vector<Common::ParamPackage> GetInputDevices() const override;
@@ -58,28 +59,38 @@ public:
std::string GetHatButtonName(u8 direction_value) const override;
u8 GetHatButtonId(const std::string& direction_name) const override;
+ bool IsStickInverted(const Common::ParamPackage& params) override;
+
Common::Input::VibrationError SetRumble(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
private:
+ struct VibrationRequest {
+ PadIdentifier identifier;
+ Common::Input::VibrationStatus vibration;
+ };
+
void InitJoystick(int joystick_index);
void CloseJoystick(SDL_Joystick* sdl_joystick);
/// Needs to be called before SDL_QuitSubSystem.
void CloseJoysticks();
- Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis,
- float value = 0.1f) const;
- Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid,
+ /// Takes all vibrations from the queue and sends the command to the controller
+ void SendVibrations();
+
+ Common::ParamPackage BuildAnalogParamPackageForButton(int port, const Common::UUID& guid,
+ s32 axis, float value = 0.1f) const;
+ Common::ParamPackage BuildButtonParamPackageForButton(int port, const Common::UUID& guid,
s32 button) const;
- Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat,
+ Common::ParamPackage BuildHatParamPackageForButton(int port, const Common::UUID& guid, s32 hat,
u8 value) const;
- Common::ParamPackage BuildMotionParam(int port, std::string guid) const;
+ Common::ParamPackage BuildMotionParam(int port, const Common::UUID& guid) const;
Common::ParamPackage BuildParamPackageForBinding(
- int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const;
+ int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const;
Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
int axis_y, float offset_x,
@@ -105,13 +116,17 @@ private:
/// Returns true if the button is on the left joycon
bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const;
+ /// Queue of vibration request to controllers
+ Common::SPSCQueue<VibrationRequest> vibration_queue;
+
/// Map of GUID of a list of corresponding virtual Joysticks
- std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
+ std::unordered_map<Common::UUID, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
std::mutex joystick_map_mutex;
bool start_thread = false;
std::atomic<bool> initialized = false;
std::thread poll_thread;
+ std::thread vibration_thread;
};
} // namespace InputCommon
diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp
index 944e141bf..21c6ed405 100644
--- a/src/input_common/drivers/tas_input.cpp
+++ b/src/input_common/drivers/tas_input.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <fmt/format.h>
diff --git a/src/input_common/drivers/tas_input.h b/src/input_common/drivers/tas_input.h
index 4b4e6c417..38a27a230 100644
--- a/src/input_common/drivers/tas_input.h
+++ b/src/input_common/drivers/tas_input.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/input_common/drivers/touch_screen.cpp b/src/input_common/drivers/touch_screen.cpp
index 30c727df4..1753e0893 100644
--- a/src/input_common/drivers/touch_screen.cpp
+++ b/src/input_common/drivers/touch_screen.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/param_package.h"
#include "input_common/drivers/touch_screen.h"
@@ -15,38 +14,93 @@ constexpr PadIdentifier identifier = {
TouchScreen::TouchScreen(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
PreSetController(identifier);
+ ReleaseAllTouch();
}
-void TouchScreen::TouchMoved(float x, float y, std::size_t finger) {
- if (finger >= 16) {
+void TouchScreen::TouchMoved(float x, float y, std::size_t finger_id) {
+ const auto index = GetIndexFromFingerId(finger_id);
+ if (!index) {
+ // Touch doesn't exist handle it as a new one
+ TouchPressed(x, y, finger_id);
return;
}
- TouchPressed(x, y, finger);
+ const auto i = index.value();
+ fingers[i].is_active = true;
+ SetButton(identifier, static_cast<int>(i), true);
+ SetAxis(identifier, static_cast<int>(i * 2), x);
+ SetAxis(identifier, static_cast<int>(i * 2 + 1), y);
}
-void TouchScreen::TouchPressed(float x, float y, std::size_t finger) {
- if (finger >= 16) {
+void TouchScreen::TouchPressed(float x, float y, std::size_t finger_id) {
+ if (GetIndexFromFingerId(finger_id)) {
+ // Touch already exist. Just update the data
+ TouchMoved(x, y, finger_id);
return;
}
- SetButton(identifier, static_cast<int>(finger), true);
- SetAxis(identifier, static_cast<int>(finger * 2), x);
- SetAxis(identifier, static_cast<int>(finger * 2 + 1), y);
+ const auto index = GetNextFreeIndex();
+ if (!index) {
+ // No free entries. Ignore input
+ return;
+ }
+ const auto i = index.value();
+ fingers[i].is_enabled = true;
+ fingers[i].finger_id = finger_id;
+ TouchMoved(x, y, finger_id);
}
-void TouchScreen::TouchReleased(std::size_t finger) {
- if (finger >= 16) {
+void TouchScreen::TouchReleased(std::size_t finger_id) {
+ const auto index = GetIndexFromFingerId(finger_id);
+ if (!index) {
return;
}
- SetButton(identifier, static_cast<int>(finger), false);
- SetAxis(identifier, static_cast<int>(finger * 2), 0.0f);
- SetAxis(identifier, static_cast<int>(finger * 2 + 1), 0.0f);
+ const auto i = index.value();
+ fingers[i].is_enabled = false;
+ SetButton(identifier, static_cast<int>(i), false);
+ SetAxis(identifier, static_cast<int>(i * 2), 0.0f);
+ SetAxis(identifier, static_cast<int>(i * 2 + 1), 0.0f);
+}
+
+std::optional<std::size_t> TouchScreen::GetIndexFromFingerId(std::size_t finger_id) const {
+ for (std::size_t index = 0; index < MAX_FINGER_COUNT; ++index) {
+ const auto& finger = fingers[index];
+ if (!finger.is_enabled) {
+ continue;
+ }
+ if (finger.finger_id == finger_id) {
+ return index;
+ }
+ }
+ return std::nullopt;
+}
+
+std::optional<std::size_t> TouchScreen::GetNextFreeIndex() const {
+ for (std::size_t index = 0; index < MAX_FINGER_COUNT; ++index) {
+ if (!fingers[index].is_enabled) {
+ return index;
+ }
+ }
+ return std::nullopt;
+}
+
+void TouchScreen::ClearActiveFlag() {
+ for (auto& finger : fingers) {
+ finger.is_active = false;
+ }
+}
+
+void TouchScreen::ReleaseInactiveTouch() {
+ for (const auto& finger : fingers) {
+ if (!finger.is_active) {
+ TouchReleased(finger.finger_id);
+ }
+ }
}
void TouchScreen::ReleaseAllTouch() {
- for (int index = 0; index < 16; ++index) {
- SetButton(identifier, index, false);
- SetAxis(identifier, index * 2, 0.0f);
- SetAxis(identifier, index * 2 + 1, 0.0f);
+ for (const auto& finger : fingers) {
+ if (finger.is_enabled) {
+ TouchReleased(finger.finger_id);
+ }
}
}
diff --git a/src/input_common/drivers/touch_screen.h b/src/input_common/drivers/touch_screen.h
index bf395c40b..f46036ffd 100644
--- a/src/input_common/drivers/touch_screen.h
+++ b/src/input_common/drivers/touch_screen.h
@@ -1,44 +1,67 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include <optional>
+
#include "input_common/input_engine.h"
namespace InputCommon {
/**
- * A button device factory representing a keyboard. It receives keyboard events and forward them
- * to all button devices it created.
+ * A touch device factory representing a touch screen. It receives touch events and forward them
+ * to all touch devices it created.
*/
class TouchScreen final : public InputEngine {
public:
explicit TouchScreen(std::string input_engine_);
/**
- * Signals that mouse has moved.
- * @param x the x-coordinate of the cursor
- * @param y the y-coordinate of the cursor
- * @param center_x the x-coordinate of the middle of the screen
- * @param center_y the y-coordinate of the middle of the screen
+ * Signals that touch has moved and marks this touch point as active
+ * @param x new horizontal position
+ * @param y new vertical position
+ * @param finger_id of the touch point to be updated
*/
- void TouchMoved(float x, float y, std::size_t finger);
+ void TouchMoved(float x, float y, std::size_t finger_id);
/**
- * Sets the status of all buttons bound with the key to pressed
- * @param key_code the code of the key to press
+ * Signals and creates a new touch point with this finger id
+ * @param x starting horizontal position
+ * @param y starting vertical position
+ * @param finger_id to be assigned to the new touch point
*/
- void TouchPressed(float x, float y, std::size_t finger);
+ void TouchPressed(float x, float y, std::size_t finger_id);
/**
- * Sets the status of all buttons bound with the key to released
- * @param key_code the code of the key to release
+ * Signals and resets the touch point related to the this finger id
+ * @param finger_id to be released
*/
- void TouchReleased(std::size_t finger);
+ void TouchReleased(std::size_t finger_id);
+
+ /// Resets the active flag for each touch point
+ void ClearActiveFlag();
+
+ /// Releases all touch that haven't been marked as active
+ void ReleaseInactiveTouch();
/// Resets all inputs to their initial value
void ReleaseAllTouch();
+
+private:
+ static constexpr std::size_t MAX_FINGER_COUNT = 16;
+
+ struct TouchStatus {
+ std::size_t finger_id{};
+ bool is_enabled{};
+ bool is_active{};
+ };
+
+ std::optional<std::size_t> GetIndexFromFingerId(std::size_t finger_id) const;
+
+ std::optional<std::size_t> GetNextFreeIndex() const;
+
+ std::array<TouchStatus, MAX_FINGER_COUNT> fingers{};
};
} // namespace InputCommon
diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp
index 64162f431..808b21069 100644
--- a/src/input_common/drivers/udp_client.cpp
+++ b/src/input_common/drivers/udp_client.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <random>
#include <boost/asio.hpp>
@@ -192,22 +191,22 @@ std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const {
return MAX_UDP_CLIENTS;
}
-BatteryLevel UDPClient::GetBatteryLevel(Response::Battery battery) const {
+Common::Input::BatteryLevel UDPClient::GetBatteryLevel(Response::Battery battery) const {
switch (battery) {
case Response::Battery::Dying:
- return BatteryLevel::Empty;
+ return Common::Input::BatteryLevel::Empty;
case Response::Battery::Low:
- return BatteryLevel::Critical;
+ return Common::Input::BatteryLevel::Critical;
case Response::Battery::Medium:
- return BatteryLevel::Low;
+ return Common::Input::BatteryLevel::Low;
case Response::Battery::High:
- return BatteryLevel::Medium;
+ return Common::Input::BatteryLevel::Medium;
case Response::Battery::Full:
case Response::Battery::Charged:
- return BatteryLevel::Full;
+ return Common::Input::BatteryLevel::Full;
case Response::Battery::Charging:
default:
- return BatteryLevel::Charging;
+ return Common::Input::BatteryLevel::Charging;
}
}
@@ -547,6 +546,22 @@ Common::Input::ButtonNames UDPClient::GetUIName(const Common::ParamPackage& para
return Common::Input::ButtonNames::Invalid;
}
+bool UDPClient::IsStickInverted(const Common::ParamPackage& params) {
+ if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
+ return false;
+ }
+
+ const auto x_axis = static_cast<PadAxes>(params.Get("axis_x", 0));
+ const auto y_axis = static_cast<PadAxes>(params.Get("axis_y", 0));
+ if (x_axis != PadAxes::LeftStickY && x_axis != PadAxes::RightStickY) {
+ return false;
+ }
+ if (y_axis != PadAxes::LeftStickX && y_axis != PadAxes::RightStickX) {
+ return false;
+ }
+ return true;
+}
+
void TestCommunication(const std::string& host, u16 port,
const std::function<void()>& success_callback,
const std::function<void()>& failure_callback) {
diff --git a/src/input_common/drivers/udp_client.h b/src/input_common/drivers/udp_client.h
index 76e32bd04..cea9f579a 100644
--- a/src/input_common/drivers/udp_client.h
+++ b/src/input_common/drivers/udp_client.h
@@ -1,6 +1,5 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -64,6 +63,8 @@ public:
MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
+ bool IsStickInverted(const Common::ParamPackage& params) override;
+
private:
enum class PadButton {
Undefined = 0x0000,
@@ -141,7 +142,7 @@ private:
std::size_t GetClientNumber(std::string_view host, u16 port) const;
// Translates UDP battery level to input engine battery level
- BatteryLevel GetBatteryLevel(Response::Battery battery) const;
+ Common::Input::BatteryLevel GetBatteryLevel(Response::Battery battery) const;
void OnVersion(Response::Version);
void OnPortInfo(Response::PortInfo);
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp
new file mode 100644
index 000000000..0cd5129da
--- /dev/null
+++ b/src/input_common/drivers/virtual_amiibo.cpp
@@ -0,0 +1,101 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <cstring>
+#include <fmt/format.h>
+
+#include "common/fs/file.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "input_common/drivers/virtual_amiibo.h"
+
+namespace InputCommon {
+constexpr PadIdentifier identifier = {
+ .guid = Common::UUID{},
+ .port = 0,
+ .pad = 0,
+};
+
+VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move(input_engine_)) {}
+
+VirtualAmiibo::~VirtualAmiibo() = default;
+
+Common::Input::PollingError VirtualAmiibo::SetPollingMode(
+ [[maybe_unused]] const PadIdentifier& identifier_,
+ const Common::Input::PollingMode polling_mode_) {
+ polling_mode = polling_mode_;
+
+ if (polling_mode == Common::Input::PollingMode::NFC) {
+ if (state == State::Initialized) {
+ state = State::WaitingForAmiibo;
+ }
+ } else {
+ if (state == State::AmiiboIsOpen) {
+ CloseAmiibo();
+ }
+ }
+
+ return Common::Input::PollingError::None;
+}
+
+Common::Input::NfcState VirtualAmiibo::SupportsNfc(
+ [[maybe_unused]] const PadIdentifier& identifier_) const {
+ return Common::Input::NfcState::Success;
+}
+
+Common::Input::NfcState VirtualAmiibo::WriteNfcData(
+ [[maybe_unused]] const PadIdentifier& identifier_, const std::vector<u8>& data) {
+ const Common::FS::IOFile amiibo_file{file_path, Common::FS::FileAccessMode::ReadWrite,
+ Common::FS::FileType::BinaryFile};
+
+ if (!amiibo_file.IsOpen()) {
+ LOG_ERROR(Core, "Amiibo is already on use");
+ return Common::Input::NfcState::WriteFailed;
+ }
+
+ if (!amiibo_file.Write(data)) {
+ LOG_ERROR(Service_NFP, "Error writting to file");
+ return Common::Input::NfcState::WriteFailed;
+ }
+
+ return Common::Input::NfcState::Success;
+}
+
+VirtualAmiibo::State VirtualAmiibo::GetCurrentState() const {
+ return state;
+}
+
+VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) {
+ const Common::FS::IOFile amiibo_file{filename, Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+
+ if (state != State::WaitingForAmiibo) {
+ return Info::WrongDeviceState;
+ }
+
+ if (!amiibo_file.IsOpen()) {
+ return Info::UnableToLoad;
+ }
+
+ amiibo_data.resize(amiibo_size);
+
+ if (amiibo_file.Read(amiibo_data) < amiibo_size_without_password) {
+ return Info::NotAnAmiibo;
+ }
+
+ file_path = filename;
+ state = State::AmiiboIsOpen;
+ SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data});
+ return Info::Success;
+}
+
+VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() {
+ state = polling_mode == Common::Input::PollingMode::NFC ? State::WaitingForAmiibo
+ : State::Initialized;
+ SetNfc(identifier, {Common::Input::NfcState::AmiiboRemoved, {}});
+ return Info::Success;
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h
new file mode 100644
index 000000000..9eac07544
--- /dev/null
+++ b/src/input_common/drivers/virtual_amiibo.h
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+#include <vector>
+
+#include "common/common_types.h"
+#include "input_common/input_engine.h"
+
+namespace Common::FS {
+class IOFile;
+}
+
+namespace InputCommon {
+
+class VirtualAmiibo final : public InputEngine {
+public:
+ enum class State {
+ Initialized,
+ WaitingForAmiibo,
+ AmiiboIsOpen,
+ };
+
+ enum class Info {
+ Success,
+ UnableToLoad,
+ NotAnAmiibo,
+ WrongDeviceState,
+ Unknown,
+ };
+
+ explicit VirtualAmiibo(std::string input_engine_);
+ ~VirtualAmiibo() override;
+
+ // Sets polling mode to a controller
+ Common::Input::PollingError SetPollingMode(
+ const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;
+
+ Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
+
+ Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
+ const std::vector<u8>& data) override;
+
+ State GetCurrentState() const;
+
+ Info LoadAmiibo(const std::string& amiibo_file);
+ Info CloseAmiibo();
+
+private:
+ static constexpr std::size_t amiibo_size = 0x21C;
+ static constexpr std::size_t amiibo_size_without_password = amiibo_size - 0x8;
+
+ std::string file_path{};
+ State state{State::Initialized};
+ std::vector<u8> amiibo_data;
+ Common::Input::PollingMode polling_mode{Common::Input::PollingMode::Pasive};
+};
+} // namespace InputCommon
diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp
index 31e6f62ab..536d413a5 100644
--- a/src/input_common/helpers/stick_from_buttons.cpp
+++ b/src/input_common/helpers/stick_from_buttons.cpp
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <cmath>
diff --git a/src/input_common/helpers/stick_from_buttons.h b/src/input_common/helpers/stick_from_buttons.h
index 437ace4f7..e8d865743 100644
--- a/src/input_common/helpers/stick_from_buttons.h
+++ b/src/input_common/helpers/stick_from_buttons.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/input_common/helpers/touch_from_buttons.cpp b/src/input_common/helpers/touch_from_buttons.cpp
index f1b57d03a..da4a3dca5 100644
--- a/src/input_common/helpers/touch_from_buttons.cpp
+++ b/src/input_common/helpers/touch_from_buttons.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2020 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "common/settings.h"
diff --git a/src/input_common/helpers/touch_from_buttons.h b/src/input_common/helpers/touch_from_buttons.h
index 628f18215..c6cb3ab3c 100644
--- a/src/input_common/helpers/touch_from_buttons.h
+++ b/src/input_common/helpers/touch_from_buttons.h
@@ -1,6 +1,5 @@
-// Copyright 2020 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2020 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/input_common/helpers/udp_protocol.cpp b/src/input_common/helpers/udp_protocol.cpp
index cdeab7e11..994380d21 100644
--- a/src/input_common/helpers/udp_protocol.cpp
+++ b/src/input_common/helpers/udp_protocol.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstddef>
#include <cstring>
diff --git a/src/input_common/helpers/udp_protocol.h b/src/input_common/helpers/udp_protocol.h
index 2d5d54ddb..d9643ffe0 100644
--- a/src/input_common/helpers/udp_protocol.h
+++ b/src/input_common/helpers/udp_protocol.h
@@ -1,6 +1,5 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,9 +7,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>
-#include "common/bit_field.h"
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
#include "common/swap.h"
namespace InputCommon::CemuhookUDP {
@@ -77,7 +84,7 @@ enum RegisterFlags : u8 {
struct Version {};
/**
* Requests the server to send information about what controllers are plugged into the ports
- * In citra's case, we only have one controller, so for simplicity's sake, we can just send a
+ * In yuzu's case, we only have one controller, so for simplicity's sake, we can just send a
* request explicitly for the first controller port and leave it at that. In the future it would be
* nice to make this configurable
*/
diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp
index 65ae1b848..61cfd0911 100644
--- a/src/input_common/input_engine.cpp
+++ b/src/input_common/input_engine.cpp
@@ -1,45 +1,43 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
-#include "common/param_package.h"
#include "input_common/input_engine.h"
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);
@@ -50,7 +48,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);
@@ -61,7 +59,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);
@@ -70,9 +68,9 @@ void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value)
TriggerOnAxisChange(identifier, axis, value);
}
-void InputEngine::SetBattery(const PadIdentifier& identifier, BatteryLevel 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;
@@ -83,7 +81,7 @@ void InputEngine::SetBattery(const PadIdentifier& identifier, BatteryLevel value
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,8 +90,31 @@ void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const B
TriggerOnMotionChange(identifier, motion, value);
}
+void InputEngine::SetCamera(const PadIdentifier& identifier,
+ const Common::Input::CameraStatus& value) {
+ {
+ std::scoped_lock lock{mutex};
+ ControllerData& controller = controller_list.at(identifier);
+ if (!configuring) {
+ controller.camera = value;
+ }
+ }
+ TriggerOnCameraChange(identifier, value);
+}
+
+void InputEngine::SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value) {
+ {
+ std::scoped_lock lock{mutex};
+ ControllerData& controller = controller_list.at(identifier);
+ if (!configuring) {
+ controller.nfc = value;
+ }
+ }
+ TriggerOnNfcChange(identifier, value);
+}
+
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(),
@@ -110,7 +131,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(),
@@ -127,7 +148,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,20 +164,20 @@ f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) const {
return axis_iter->second;
}
-BatteryLevel InputEngine::GetBattery(const PadIdentifier& identifier) const {
- std::lock_guard lock{mutex};
+Common::Input::BatteryLevel InputEngine::GetBattery(const PadIdentifier& identifier) const {
+ 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(),
identifier.pad, identifier.port);
- return BatteryLevel::Charging;
+ return Common::Input::BatteryLevel::Charging;
}
const ControllerData& controller = controller_iter->second;
return controller.battery;
}
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(),
@@ -167,6 +188,30 @@ BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion)
return controller.motions.at(motion);
}
+Common::Input::CameraStatus InputEngine::GetCamera(const PadIdentifier& identifier) const {
+ 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(),
+ identifier.pad, identifier.port);
+ return {};
+ }
+ const ControllerData& controller = controller_iter->second;
+ return controller.camera;
+}
+
+Common::Input::NfcStatus InputEngine::GetNfc(const PadIdentifier& identifier) const {
+ 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(),
+ identifier.pad, identifier.port);
+ return {};
+ }
+ const ControllerData& controller = controller_iter->second;
+ return controller.nfc;
+}
+
void InputEngine::ResetButtonState() {
for (const auto& controller : controller_list) {
for (const auto& button : controller.second.buttons) {
@@ -187,7 +232,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)) {
@@ -215,7 +260,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)) {
@@ -244,7 +289,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,8 +315,8 @@ void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis,
}
void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
- [[maybe_unused]] BatteryLevel value) {
- std::lock_guard lock{mutex_callback};
+ [[maybe_unused]] Common::Input::BatteryLevel value) {
+ 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)) {
@@ -285,7 +330,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)) {
@@ -319,6 +364,34 @@ void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int mot
});
}
+void InputEngine::TriggerOnCameraChange(const PadIdentifier& identifier,
+ [[maybe_unused]] const Common::Input::CameraStatus& value) {
+ std::scoped_lock lock{mutex_callback};
+ for (const auto& poller_pair : callback_list) {
+ const InputIdentifier& poller = poller_pair.second;
+ if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Camera, 0)) {
+ continue;
+ }
+ if (poller.callback.on_change) {
+ poller.callback.on_change();
+ }
+ }
+}
+
+void InputEngine::TriggerOnNfcChange(const PadIdentifier& identifier,
+ [[maybe_unused]] const Common::Input::NfcStatus& value) {
+ std::scoped_lock lock{mutex_callback};
+ for (const auto& poller_pair : callback_list) {
+ const InputIdentifier& poller = poller_pair.second;
+ if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Nfc, 0)) {
+ continue;
+ }
+ if (poller.callback.on_change) {
+ poller.callback.on_change();
+ }
+ }
+}
+
bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier,
const PadIdentifier& identifier, EngineInputType type,
int index) const {
@@ -347,18 +420,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/input_common/input_engine.h b/src/input_common/input_engine.h
index c6c027aef..cfbdb26bd 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -34,24 +33,16 @@ struct BasicMotion {
u64 delta_timestamp{};
};
-// Stages of a battery charge
-enum class BatteryLevel {
- Empty,
- Critical,
- Low,
- Medium,
- Full,
- Charging,
-};
-
// Types of input that are stored in the engine
enum class EngineInputType {
None,
+ Analog,
+ Battery,
Button,
+ Camera,
HatButton,
- Analog,
Motion,
- Battery,
+ Nfc,
};
namespace std {
@@ -126,10 +117,29 @@ public:
// Sets polling mode to a controller
virtual Common::Input::PollingError SetPollingMode(
[[maybe_unused]] const PadIdentifier& identifier,
- [[maybe_unused]] const Common::Input::PollingMode vibration) {
+ [[maybe_unused]] const Common::Input::PollingMode polling_mode) {
return Common::Input::PollingError::NotSupported;
}
+ // Sets camera format to a controller
+ virtual Common::Input::CameraError SetCameraFormat(
+ [[maybe_unused]] const PadIdentifier& identifier,
+ [[maybe_unused]] Common::Input::CameraFormat camera_format) {
+ return Common::Input::CameraError::NotSupported;
+ }
+
+ // Request nfc data from a controller
+ virtual Common::Input::NfcState SupportsNfc(
+ [[maybe_unused]] const PadIdentifier& identifier) const {
+ return Common::Input::NfcState::NotSupported;
+ }
+
+ // Writes data to an nfc tag
+ virtual Common::Input::NfcState WriteNfcData([[maybe_unused]] const PadIdentifier& identifier,
+ [[maybe_unused]] const std::vector<u8>& data) {
+ return Common::Input::NfcState::NotSupported;
+ }
+
// Returns the engine name
[[nodiscard]] const std::string& GetEngineName() const;
@@ -167,6 +177,11 @@ public:
return 0;
}
+ /// Returns true if axis of a stick aren't mapped in the correct direction
+ virtual bool IsStickInverted([[maybe_unused]] const Common::ParamPackage& params) {
+ return false;
+ }
+
void PreSetController(const PadIdentifier& identifier);
void PreSetButton(const PadIdentifier& identifier, int button);
void PreSetHatButton(const PadIdentifier& identifier, int button);
@@ -178,8 +193,10 @@ public:
bool GetButton(const PadIdentifier& identifier, int button) const;
bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;
f32 GetAxis(const PadIdentifier& identifier, int axis) const;
- BatteryLevel GetBattery(const PadIdentifier& identifier) const;
+ Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const;
BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
+ Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const;
+ Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const;
int SetCallback(InputIdentifier input_identifier);
void SetMappingCallback(MappingCallback callback);
@@ -189,8 +206,10 @@ protected:
void SetButton(const PadIdentifier& identifier, int button, bool value);
void SetHatButton(const PadIdentifier& identifier, int button, u8 value);
void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
- void SetBattery(const PadIdentifier& identifier, BatteryLevel value);
+ void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);
+ void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value);
+ void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);
virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
return "Unknown";
@@ -202,15 +221,20 @@ private:
std::unordered_map<int, u8> hat_buttons;
std::unordered_map<int, float> axes;
std::unordered_map<int, BasicMotion> motions;
- BatteryLevel battery{};
+ Common::Input::BatteryLevel battery{};
+ Common::Input::CameraStatus camera{};
+ Common::Input::NfcStatus nfc{};
};
void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);
void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value);
- void TriggerOnBatteryChange(const PadIdentifier& identifier, BatteryLevel value);
+ void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
void TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
const BasicMotion& value);
+ void TriggerOnCameraChange(const PadIdentifier& identifier,
+ const Common::Input::CameraStatus& value);
+ void TriggerOnNfcChange(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);
bool IsInputIdentifierEqual(const InputIdentifier& input_identifier,
const PadIdentifier& identifier, EngineInputType type,
diff --git a/src/input_common/input_mapping.cpp b/src/input_common/input_mapping.cpp
index fb78093b8..0fa4b1ddb 100644
--- a/src/input_common/input_mapping.cpp
+++ b/src/input_common/input_mapping.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h"
#include "input_common/input_engine.h"
diff --git a/src/input_common/input_mapping.h b/src/input_common/input_mapping.h
index e0dfbc7ad..79bd083e0 100644
--- a/src/input_common/input_mapping.h
+++ b/src/input_common/input_mapping.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index 7f3c08597..75705b67e 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
#include "common/input.h"
@@ -470,7 +469,7 @@ public:
}
Common::Input::BatteryStatus GetStatus() const {
- return static_cast<Common::Input::BatteryLevel>(input_engine->GetBattery(identifier));
+ return input_engine->GetBattery(identifier);
}
void ForceUpdate() override {
@@ -665,6 +664,88 @@ private:
InputEngine* input_engine;
};
+class InputFromCamera final : public Common::Input::InputDevice {
+public:
+ explicit InputFromCamera(PadIdentifier identifier_, InputEngine* input_engine_)
+ : identifier(identifier_), input_engine(input_engine_) {
+ UpdateCallback engine_callback{[this]() { OnChange(); }};
+ const InputIdentifier input_identifier{
+ .identifier = identifier,
+ .type = EngineInputType::Camera,
+ .index = 0,
+ .callback = engine_callback,
+ };
+ callback_key = input_engine->SetCallback(input_identifier);
+ }
+
+ ~InputFromCamera() override {
+ input_engine->DeleteCallback(callback_key);
+ }
+
+ Common::Input::CameraStatus GetStatus() const {
+ return input_engine->GetCamera(identifier);
+ }
+
+ void ForceUpdate() override {
+ OnChange();
+ }
+
+ void OnChange() {
+ const Common::Input::CallbackStatus status{
+ .type = Common::Input::InputType::IrSensor,
+ .camera_status = GetStatus(),
+ };
+
+ TriggerOnChange(status);
+ }
+
+private:
+ const PadIdentifier identifier;
+ int callback_key;
+ InputEngine* input_engine;
+};
+
+class InputFromNfc final : public Common::Input::InputDevice {
+public:
+ explicit InputFromNfc(PadIdentifier identifier_, InputEngine* input_engine_)
+ : identifier(identifier_), input_engine(input_engine_) {
+ UpdateCallback engine_callback{[this]() { OnChange(); }};
+ const InputIdentifier input_identifier{
+ .identifier = identifier,
+ .type = EngineInputType::Nfc,
+ .index = 0,
+ .callback = engine_callback,
+ };
+ callback_key = input_engine->SetCallback(input_identifier);
+ }
+
+ ~InputFromNfc() override {
+ input_engine->DeleteCallback(callback_key);
+ }
+
+ Common::Input::NfcStatus GetStatus() const {
+ return input_engine->GetNfc(identifier);
+ }
+
+ void ForceUpdate() override {
+ OnChange();
+ }
+
+ void OnChange() {
+ const Common::Input::CallbackStatus status{
+ .type = Common::Input::InputType::Nfc,
+ .nfc_status = GetStatus(),
+ };
+
+ TriggerOnChange(status);
+ }
+
+private:
+ const PadIdentifier identifier;
+ int callback_key;
+ InputEngine* input_engine;
+};
+
class OutputFromIdentifier final : public Common::Input::OutputDevice {
public:
explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
@@ -683,6 +764,18 @@ public:
return input_engine->SetPollingMode(identifier, polling_mode);
}
+ Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override {
+ return input_engine->SetCameraFormat(identifier, camera_format);
+ }
+
+ Common::Input::NfcState SupportsNfc() const override {
+ return input_engine->SupportsNfc(identifier);
+ }
+
+ Common::Input::NfcState WriteNfcData(const std::vector<u8>& data) override {
+ return input_engine->WriteNfcData(identifier, data);
+ }
+
private:
const PadIdentifier identifier;
InputEngine* input_engine;
@@ -733,7 +826,7 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateHatButtonDevice(
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateStickDevice(
const Common::ParamPackage& params) {
const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f);
- const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
+ const auto range = std::clamp(params.Get("range", 0.95f), 0.25f, 1.50f);
const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
const PadIdentifier identifier = {
.guid = Common::UUID{params.Get("guid", "")},
@@ -780,6 +873,7 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice(
.threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f),
.offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f),
.inverted = params.Get("invert", "+") == "-",
+ .toggle = static_cast<bool>(params.Get("toggle", false)),
};
input_engine->PreSetController(identifier);
input_engine->PreSetAxis(identifier, axis);
@@ -921,6 +1015,30 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
properties_y, properties_z, input_engine.get());
}
+std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateCameraDevice(
+ const Common::ParamPackage& params) {
+ const PadIdentifier identifier = {
+ .guid = Common::UUID{params.Get("guid", "")},
+ .port = static_cast<std::size_t>(params.Get("port", 0)),
+ .pad = static_cast<std::size_t>(params.Get("pad", 0)),
+ };
+
+ input_engine->PreSetController(identifier);
+ return std::make_unique<InputFromCamera>(identifier, input_engine.get());
+}
+
+std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateNfcDevice(
+ const Common::ParamPackage& params) {
+ const PadIdentifier identifier = {
+ .guid = Common::UUID{params.Get("guid", "")},
+ .port = static_cast<std::size_t>(params.Get("port", 0)),
+ .pad = static_cast<std::size_t>(params.Get("pad", 0)),
+ };
+
+ input_engine->PreSetController(identifier);
+ return std::make_unique<InputFromNfc>(identifier, input_engine.get());
+}
+
InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_)
: input_engine(std::move(input_engine_)) {}
@@ -929,6 +1047,12 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
if (params.Has("battery")) {
return CreateBatteryDevice(params);
}
+ if (params.Has("camera")) {
+ return CreateCameraDevice(params);
+ }
+ if (params.Has("nfc")) {
+ return CreateNfcDevice(params);
+ }
if (params.Has("button") && params.Has("axis")) {
return CreateTriggerDevice(params);
}
diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h
index 8a0977d58..d7db13ce4 100644
--- a/src/input_common/input_poller.h
+++ b/src/input_common/input_poller.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -212,6 +211,27 @@ private:
*/
std::unique_ptr<Common::Input::InputDevice> CreateMotionDevice(Common::ParamPackage params);
+ /**
+ * Creates a camera device from the parameters given.
+ * @param params contains parameters for creating the device:
+ * - "guid": text string for identifying controllers
+ * - "port": port of the connected device
+ * - "pad": slot of the connected controller
+ * @returns a unique input device with the parameters specified
+ */
+ std::unique_ptr<Common::Input::InputDevice> CreateCameraDevice(
+ const Common::ParamPackage& params);
+
+ /**
+ * Creates a nfc device from the parameters given.
+ * @param params contains parameters for creating the device:
+ * - "guid": text string for identifying controllers
+ * - "port": port of the connected device
+ * - "pad": slot of the connected controller
+ * @returns a unique input device with the parameters specified
+ */
+ std::unique_ptr<Common::Input::InputDevice> CreateNfcDevice(const Common::ParamPackage& params);
+
std::shared_ptr<InputEngine> input_engine;
};
} // namespace InputCommon
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index a4d7ed645..b2064ef95 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -1,17 +1,17 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
-#include <thread>
#include "common/input.h"
#include "common/param_package.h"
+#include "input_common/drivers/camera.h"
#include "input_common/drivers/gc_adapter.h"
#include "input_common/drivers/keyboard.h"
#include "input_common/drivers/mouse.h"
#include "input_common/drivers/tas_input.h"
#include "input_common/drivers/touch_screen.h"
#include "input_common/drivers/udp_client.h"
+#include "input_common/drivers/virtual_amiibo.h"
#include "input_common/helpers/stick_from_buttons.h"
#include "input_common/helpers/touch_from_buttons.h"
#include "input_common/input_engine.h"
@@ -79,6 +79,24 @@ struct InputSubsystem::Impl {
Common::Input::RegisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName(),
tas_output_factory);
+ camera = std::make_shared<Camera>("camera");
+ camera->SetMappingCallback(mapping_callback);
+ camera_input_factory = std::make_shared<InputFactory>(camera);
+ camera_output_factory = std::make_shared<OutputFactory>(camera);
+ Common::Input::RegisterFactory<Common::Input::InputDevice>(camera->GetEngineName(),
+ camera_input_factory);
+ Common::Input::RegisterFactory<Common::Input::OutputDevice>(camera->GetEngineName(),
+ camera_output_factory);
+
+ virtual_amiibo = std::make_shared<VirtualAmiibo>("virtual_amiibo");
+ virtual_amiibo->SetMappingCallback(mapping_callback);
+ virtual_amiibo_input_factory = std::make_shared<InputFactory>(virtual_amiibo);
+ virtual_amiibo_output_factory = std::make_shared<OutputFactory>(virtual_amiibo);
+ Common::Input::RegisterFactory<Common::Input::InputDevice>(virtual_amiibo->GetEngineName(),
+ virtual_amiibo_input_factory);
+ Common::Input::RegisterFactory<Common::Input::OutputDevice>(virtual_amiibo->GetEngineName(),
+ virtual_amiibo_output_factory);
+
#ifdef HAVE_SDL2
sdl = std::make_shared<SDLDriver>("sdl");
sdl->SetMappingCallback(mapping_callback);
@@ -242,6 +260,28 @@ struct InputSubsystem::Impl {
return Common::Input::ButtonNames::Invalid;
}
+ bool IsStickInverted(const Common::ParamPackage& params) {
+ const std::string engine = params.Get("engine", "");
+ if (engine == mouse->GetEngineName()) {
+ return mouse->IsStickInverted(params);
+ }
+ if (engine == gcadapter->GetEngineName()) {
+ return gcadapter->IsStickInverted(params);
+ }
+ if (engine == udp_client->GetEngineName()) {
+ return udp_client->IsStickInverted(params);
+ }
+ if (engine == tas_input->GetEngineName()) {
+ return tas_input->IsStickInverted(params);
+ }
+#ifdef HAVE_SDL2
+ if (engine == sdl->GetEngineName()) {
+ return sdl->IsStickInverted(params);
+ }
+#endif
+ return false;
+ }
+
bool IsController(const Common::ParamPackage& params) {
const std::string engine = params.Get("engine", "");
if (engine == mouse->GetEngineName()) {
@@ -296,6 +336,8 @@ struct InputSubsystem::Impl {
std::shared_ptr<TouchScreen> touch_screen;
std::shared_ptr<TasInput::Tas> tas_input;
std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
+ std::shared_ptr<Camera> camera;
+ std::shared_ptr<VirtualAmiibo> virtual_amiibo;
std::shared_ptr<InputFactory> keyboard_factory;
std::shared_ptr<InputFactory> mouse_factory;
@@ -303,12 +345,16 @@ struct InputSubsystem::Impl {
std::shared_ptr<InputFactory> touch_screen_factory;
std::shared_ptr<InputFactory> udp_client_input_factory;
std::shared_ptr<InputFactory> tas_input_factory;
+ std::shared_ptr<InputFactory> camera_input_factory;
+ std::shared_ptr<InputFactory> virtual_amiibo_input_factory;
std::shared_ptr<OutputFactory> keyboard_output_factory;
std::shared_ptr<OutputFactory> mouse_output_factory;
std::shared_ptr<OutputFactory> gcadapter_output_factory;
std::shared_ptr<OutputFactory> udp_client_output_factory;
std::shared_ptr<OutputFactory> tas_output_factory;
+ std::shared_ptr<OutputFactory> camera_output_factory;
+ std::shared_ptr<OutputFactory> virtual_amiibo_output_factory;
#ifdef HAVE_SDL2
std::shared_ptr<SDLDriver> sdl;
@@ -361,6 +407,22 @@ const TasInput::Tas* InputSubsystem::GetTas() const {
return impl->tas_input.get();
}
+Camera* InputSubsystem::GetCamera() {
+ return impl->camera.get();
+}
+
+const Camera* InputSubsystem::GetCamera() const {
+ return impl->camera.get();
+}
+
+VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() {
+ return impl->virtual_amiibo.get();
+}
+
+const VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() const {
+ return impl->virtual_amiibo.get();
+}
+
std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
return impl->GetInputDevices();
}
@@ -385,6 +447,13 @@ bool InputSubsystem::IsController(const Common::ParamPackage& params) const {
return impl->IsController(params);
}
+bool InputSubsystem::IsStickInverted(const Common::ParamPackage& params) const {
+ if (params.Has("axis_x") && params.Has("axis_y")) {
+ return impl->IsStickInverted(params);
+ }
+ return false;
+}
+
void InputSubsystem::ReloadInputDevices() {
impl->udp_client.get()->ReloadSockets();
}
diff --git a/src/input_common/main.h b/src/input_common/main.h
index baf107e0f..ced252383 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -30,9 +29,11 @@ enum Values : int;
}
namespace InputCommon {
+class Camera;
class Keyboard;
class Mouse;
class TouchScreen;
+class VirtualAmiibo;
struct MappingData;
} // namespace InputCommon
@@ -92,9 +93,21 @@ public:
/// Retrieves the underlying tas input device.
[[nodiscard]] TasInput::Tas* GetTas();
- /// Retrieves the underlying tas input device.
+ /// Retrieves the underlying tas input device.
[[nodiscard]] const TasInput::Tas* GetTas() const;
+ /// Retrieves the underlying camera input device.
+ [[nodiscard]] Camera* GetCamera();
+
+ /// Retrieves the underlying camera input device.
+ [[nodiscard]] const Camera* GetCamera() const;
+
+ /// Retrieves the underlying virtual amiibo input device.
+ [[nodiscard]] VirtualAmiibo* GetVirtualAmiibo();
+
+ /// Retrieves the underlying virtual amiibo input device.
+ [[nodiscard]] const VirtualAmiibo* GetVirtualAmiibo() const;
+
/**
* Returns all available input devices that this Factory can create a new device with.
* Each returned ParamPackage should have a `display` field used for display, a `engine` field
@@ -119,6 +132,9 @@ public:
/// Returns true if device is a controller.
[[nodiscard]] bool IsController(const Common::ParamPackage& params) const;
+ /// Returns true if axis of a stick aren't mapped in the correct direction
+ [[nodiscard]] bool IsStickInverted(const Common::ParamPackage& device) const;
+
/// Reloads the input devices.
void ReloadInputDevices();
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt
new file mode 100644
index 000000000..6f8ca4b90
--- /dev/null
+++ b/src/network/CMakeLists.txt
@@ -0,0 +1,25 @@
+# SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+add_library(network STATIC
+ announce_multiplayer_session.cpp
+ announce_multiplayer_session.h
+ network.cpp
+ network.h
+ packet.cpp
+ packet.h
+ room.cpp
+ room.h
+ room_member.cpp
+ room_member.h
+ verify_user.cpp
+ verify_user.h
+)
+
+create_target_directory_groups(network)
+
+target_link_libraries(network PRIVATE common enet Boost::boost)
+if (ENABLE_WEB_SERVICE)
+ target_compile_definitions(network PRIVATE -DENABLE_WEB_SERVICE)
+ target_link_libraries(network PRIVATE web_service)
+endif()
diff --git a/src/network/announce_multiplayer_session.cpp b/src/network/announce_multiplayer_session.cpp
new file mode 100644
index 000000000..6737ce85a
--- /dev/null
+++ b/src/network/announce_multiplayer_session.cpp
@@ -0,0 +1,164 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <chrono>
+#include <future>
+#include <vector>
+#include "announce_multiplayer_session.h"
+#include "common/announce_multiplayer_room.h"
+#include "common/assert.h"
+#include "common/settings.h"
+#include "network/network.h"
+
+#ifdef ENABLE_WEB_SERVICE
+#include "web_service/announce_room_json.h"
+#endif
+
+namespace Core {
+
+// Time between room is announced to web_service
+static constexpr std::chrono::seconds announce_time_interval(15);
+
+AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& room_network_)
+ : room_network{room_network_} {
+#ifdef ENABLE_WEB_SERVICE
+ backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(),
+ Settings::values.yuzu_username.GetValue(),
+ Settings::values.yuzu_token.GetValue());
+#else
+ backend = std::make_unique<AnnounceMultiplayerRoom::NullBackend>();
+#endif
+}
+
+WebService::WebResult AnnounceMultiplayerSession::Register() {
+ auto room = room_network.GetRoom().lock();
+ if (!room) {
+ return WebService::WebResult{WebService::WebResult::Code::LibError,
+ "Network is not initialized", ""};
+ }
+ if (room->GetState() != Network::Room::State::Open) {
+ return WebService::WebResult{WebService::WebResult::Code::LibError, "Room is not open", ""};
+ }
+ UpdateBackendData(room);
+ WebService::WebResult result = backend->Register();
+ if (result.result_code != WebService::WebResult::Code::Success) {
+ return result;
+ }
+ LOG_INFO(WebService, "Room has been registered");
+ room->SetVerifyUID(result.returned_data);
+ registered = true;
+ return WebService::WebResult{WebService::WebResult::Code::Success, "", ""};
+}
+
+void AnnounceMultiplayerSession::Start() {
+ if (announce_multiplayer_thread) {
+ Stop();
+ }
+ shutdown_event.Reset();
+ announce_multiplayer_thread =
+ std::make_unique<std::thread>(&AnnounceMultiplayerSession::AnnounceMultiplayerLoop, this);
+}
+
+void AnnounceMultiplayerSession::Stop() {
+ if (announce_multiplayer_thread) {
+ shutdown_event.Set();
+ announce_multiplayer_thread->join();
+ announce_multiplayer_thread.reset();
+ backend->Delete();
+ registered = false;
+ }
+}
+
+AnnounceMultiplayerSession::CallbackHandle AnnounceMultiplayerSession::BindErrorCallback(
+ std::function<void(const WebService::WebResult&)> function) {
+ std::lock_guard lock(callback_mutex);
+ auto handle = std::make_shared<std::function<void(const WebService::WebResult&)>>(function);
+ error_callbacks.insert(handle);
+ return handle;
+}
+
+void AnnounceMultiplayerSession::UnbindErrorCallback(CallbackHandle handle) {
+ std::lock_guard lock(callback_mutex);
+ error_callbacks.erase(handle);
+}
+
+AnnounceMultiplayerSession::~AnnounceMultiplayerSession() {
+ Stop();
+}
+
+void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr<Network::Room> room) {
+ Network::RoomInformation room_information = room->GetRoomInformation();
+ std::vector<AnnounceMultiplayerRoom::Member> memberlist = room->GetRoomMemberList();
+ backend->SetRoomInformation(room_information.name, room_information.description,
+ room_information.port, room_information.member_slots,
+ Network::network_version, room->HasPassword(),
+ room_information.preferred_game);
+ backend->ClearPlayers();
+ for (const auto& member : memberlist) {
+ backend->AddPlayer(member);
+ }
+}
+
+void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() {
+ // Invokes all current bound error callbacks.
+ const auto ErrorCallback = [this](WebService::WebResult result) {
+ std::lock_guard lock(callback_mutex);
+ for (auto callback : error_callbacks) {
+ (*callback)(result);
+ }
+ };
+
+ if (!registered) {
+ WebService::WebResult result = Register();
+ if (result.result_code != WebService::WebResult::Code::Success) {
+ ErrorCallback(result);
+ return;
+ }
+ }
+
+ auto update_time = std::chrono::steady_clock::now();
+ std::future<WebService::WebResult> future;
+ while (!shutdown_event.WaitUntil(update_time)) {
+ update_time += announce_time_interval;
+ auto room = room_network.GetRoom().lock();
+ if (!room) {
+ break;
+ }
+ if (room->GetState() != Network::Room::State::Open) {
+ break;
+ }
+ UpdateBackendData(room);
+ WebService::WebResult result = backend->Update();
+ if (result.result_code != WebService::WebResult::Code::Success) {
+ ErrorCallback(result);
+ }
+ if (result.result_string == "404") {
+ registered = false;
+ // Needs to register the room again
+ WebService::WebResult register_result = Register();
+ if (register_result.result_code != WebService::WebResult::Code::Success) {
+ ErrorCallback(register_result);
+ }
+ }
+ }
+}
+
+AnnounceMultiplayerRoom::RoomList AnnounceMultiplayerSession::GetRoomList() {
+ return backend->GetRoomList();
+}
+
+bool AnnounceMultiplayerSession::IsRunning() const {
+ return announce_multiplayer_thread != nullptr;
+}
+
+void AnnounceMultiplayerSession::UpdateCredentials() {
+ ASSERT_MSG(!IsRunning(), "Credentials can only be updated when session is not running");
+
+#ifdef ENABLE_WEB_SERVICE
+ backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(),
+ Settings::values.yuzu_username.GetValue(),
+ Settings::values.yuzu_token.GetValue());
+#endif
+}
+
+} // namespace Core
diff --git a/src/network/announce_multiplayer_session.h b/src/network/announce_multiplayer_session.h
new file mode 100644
index 000000000..db790f7d2
--- /dev/null
+++ b/src/network/announce_multiplayer_session.h
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <set>
+#include <thread>
+#include "common/announce_multiplayer_room.h"
+#include "common/common_types.h"
+#include "common/thread.h"
+
+namespace Network {
+class Room;
+class RoomNetwork;
+} // namespace Network
+
+namespace Core {
+
+/**
+ * Instruments AnnounceMultiplayerRoom::Backend.
+ * Creates a thread that regularly updates the room information and submits them
+ * An async get of room information is also possible
+ */
+class AnnounceMultiplayerSession {
+public:
+ using CallbackHandle = std::shared_ptr<std::function<void(const WebService::WebResult&)>>;
+ AnnounceMultiplayerSession(Network::RoomNetwork& room_network_);
+ ~AnnounceMultiplayerSession();
+
+ /**
+ * Allows to bind a function that will get called if the announce encounters an error
+ * @param function The function that gets called
+ * @return A handle that can be used the unbind the function
+ */
+ CallbackHandle BindErrorCallback(std::function<void(const WebService::WebResult&)> function);
+
+ /**
+ * Unbind a function from the error callbacks
+ * @param handle The handle for the function that should get unbind
+ */
+ void UnbindErrorCallback(CallbackHandle handle);
+
+ /**
+ * Registers a room to web services
+ * @return The result of the registration attempt.
+ */
+ WebService::WebResult Register();
+
+ /**
+ * Starts the announce of a room to web services
+ */
+ void Start();
+
+ /**
+ * Stops the announce to web services
+ */
+ void Stop();
+
+ /**
+ * Returns a list of all room information the backend got
+ * @param func A function that gets executed when the async get finished, e.g. a signal
+ * @return a list of rooms received from the web service
+ */
+ AnnounceMultiplayerRoom::RoomList GetRoomList();
+
+ /**
+ * Whether the announce session is still running
+ */
+ bool IsRunning() const;
+
+ /**
+ * Recreates the backend, updating the credentials.
+ * This can only be used when the announce session is not running.
+ */
+ void UpdateCredentials();
+
+private:
+ void UpdateBackendData(std::shared_ptr<Network::Room> room);
+ void AnnounceMultiplayerLoop();
+
+ Common::Event shutdown_event;
+ std::mutex callback_mutex;
+ std::set<CallbackHandle> error_callbacks;
+ std::unique_ptr<std::thread> announce_multiplayer_thread;
+
+ /// Backend interface that logs fields
+ std::unique_ptr<AnnounceMultiplayerRoom::Backend> backend;
+
+ std::atomic_bool registered = false; ///< Whether the room has been registered
+
+ Network::RoomNetwork& room_network;
+};
+
+} // namespace Core
diff --git a/src/network/network.cpp b/src/network/network.cpp
new file mode 100644
index 000000000..6652a186b
--- /dev/null
+++ b/src/network/network.cpp
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "enet/enet.h"
+#include "network/network.h"
+
+namespace Network {
+
+RoomNetwork::RoomNetwork() {
+ m_room = std::make_shared<Room>();
+ m_room_member = std::make_shared<RoomMember>();
+}
+
+bool RoomNetwork::Init() {
+ if (enet_initialize() != 0) {
+ LOG_ERROR(Network, "Error initializing ENet");
+ return false;
+ }
+ m_room = std::make_shared<Room>();
+ m_room_member = std::make_shared<RoomMember>();
+ LOG_DEBUG(Network, "initialized OK");
+ return true;
+}
+
+std::weak_ptr<Room> RoomNetwork::GetRoom() {
+ return m_room;
+}
+
+std::weak_ptr<RoomMember> RoomNetwork::GetRoomMember() {
+ return m_room_member;
+}
+
+void RoomNetwork::Shutdown() {
+ if (m_room_member) {
+ if (m_room_member->IsConnected())
+ m_room_member->Leave();
+ m_room_member.reset();
+ }
+ if (m_room) {
+ if (m_room->GetState() == Room::State::Open)
+ m_room->Destroy();
+ m_room.reset();
+ }
+ enet_deinitialize();
+ LOG_DEBUG(Network, "shutdown OK");
+}
+
+} // namespace Network
diff --git a/src/network/network.h b/src/network/network.h
new file mode 100644
index 000000000..e4de207b2
--- /dev/null
+++ b/src/network/network.h
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include "network/room.h"
+#include "network/room_member.h"
+
+namespace Network {
+
+class RoomNetwork {
+public:
+ RoomNetwork();
+
+ /// Initializes and registers the network device, the room, and the room member.
+ bool Init();
+
+ /// Returns a pointer to the room handle
+ std::weak_ptr<Room> GetRoom();
+
+ /// Returns a pointer to the room member handle
+ std::weak_ptr<RoomMember> GetRoomMember();
+
+ /// Unregisters the network device, the room, and the room member and shut them down.
+ void Shutdown();
+
+private:
+ std::shared_ptr<RoomMember> m_room_member; ///< RoomMember (Client) for network games
+ std::shared_ptr<Room> m_room; ///< Room (Server) for network games
+};
+
+} // namespace Network
diff --git a/src/network/packet.cpp b/src/network/packet.cpp
new file mode 100644
index 000000000..0e22f1eb4
--- /dev/null
+++ b/src/network/packet.cpp
@@ -0,0 +1,262 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+#include <cstring>
+#include <string>
+#include "network/packet.h"
+
+namespace Network {
+
+#ifndef htonll
+static u64 htonll(u64 x) {
+ return ((1 == htonl(1)) ? (x) : ((uint64_t)htonl((x)&0xFFFFFFFF) << 32) | htonl((x) >> 32));
+}
+#endif
+
+#ifndef ntohll
+static u64 ntohll(u64 x) {
+ return ((1 == ntohl(1)) ? (x) : ((uint64_t)ntohl((x)&0xFFFFFFFF) << 32) | ntohl((x) >> 32));
+}
+#endif
+
+void Packet::Append(const void* in_data, std::size_t size_in_bytes) {
+ if (in_data && (size_in_bytes > 0)) {
+ std::size_t start = data.size();
+ data.resize(start + size_in_bytes);
+ std::memcpy(&data[start], in_data, size_in_bytes);
+ }
+}
+
+void Packet::Read(void* out_data, std::size_t size_in_bytes) {
+ if (out_data && CheckSize(size_in_bytes)) {
+ std::memcpy(out_data, &data[read_pos], size_in_bytes);
+ read_pos += size_in_bytes;
+ }
+}
+
+void Packet::Clear() {
+ data.clear();
+ read_pos = 0;
+ is_valid = true;
+}
+
+const void* Packet::GetData() const {
+ return !data.empty() ? &data[0] : nullptr;
+}
+
+void Packet::IgnoreBytes(u32 length) {
+ read_pos += length;
+}
+
+std::size_t Packet::GetDataSize() const {
+ return data.size();
+}
+
+bool Packet::EndOfPacket() const {
+ return read_pos >= data.size();
+}
+
+Packet::operator bool() const {
+ return is_valid;
+}
+
+Packet& Packet::Read(bool& out_data) {
+ u8 value{};
+ if (Read(value)) {
+ out_data = (value != 0);
+ }
+ return *this;
+}
+
+Packet& Packet::Read(s8& out_data) {
+ Read(&out_data, sizeof(out_data));
+ return *this;
+}
+
+Packet& Packet::Read(u8& out_data) {
+ Read(&out_data, sizeof(out_data));
+ return *this;
+}
+
+Packet& Packet::Read(s16& out_data) {
+ s16 value{};
+ Read(&value, sizeof(value));
+ out_data = ntohs(value);
+ return *this;
+}
+
+Packet& Packet::Read(u16& out_data) {
+ u16 value{};
+ Read(&value, sizeof(value));
+ out_data = ntohs(value);
+ return *this;
+}
+
+Packet& Packet::Read(s32& out_data) {
+ s32 value{};
+ Read(&value, sizeof(value));
+ out_data = ntohl(value);
+ return *this;
+}
+
+Packet& Packet::Read(u32& out_data) {
+ u32 value{};
+ Read(&value, sizeof(value));
+ out_data = ntohl(value);
+ return *this;
+}
+
+Packet& Packet::Read(s64& out_data) {
+ s64 value{};
+ Read(&value, sizeof(value));
+ out_data = ntohll(value);
+ return *this;
+}
+
+Packet& Packet::Read(u64& out_data) {
+ u64 value{};
+ Read(&value, sizeof(value));
+ out_data = ntohll(value);
+ return *this;
+}
+
+Packet& Packet::Read(float& out_data) {
+ Read(&out_data, sizeof(out_data));
+ return *this;
+}
+
+Packet& Packet::Read(double& out_data) {
+ Read(&out_data, sizeof(out_data));
+ return *this;
+}
+
+Packet& Packet::Read(char* out_data) {
+ // First extract string length
+ u32 length = 0;
+ Read(length);
+
+ if ((length > 0) && CheckSize(length)) {
+ // Then extract characters
+ std::memcpy(out_data, &data[read_pos], length);
+ out_data[length] = '\0';
+
+ // Update reading position
+ read_pos += length;
+ }
+
+ return *this;
+}
+
+Packet& Packet::Read(std::string& out_data) {
+ // First extract string length
+ u32 length = 0;
+ Read(length);
+
+ out_data.clear();
+ if ((length > 0) && CheckSize(length)) {
+ // Then extract characters
+ out_data.assign(&data[read_pos], length);
+
+ // Update reading position
+ read_pos += length;
+ }
+
+ return *this;
+}
+
+Packet& Packet::Write(bool in_data) {
+ Write(static_cast<u8>(in_data));
+ return *this;
+}
+
+Packet& Packet::Write(s8 in_data) {
+ Append(&in_data, sizeof(in_data));
+ return *this;
+}
+
+Packet& Packet::Write(u8 in_data) {
+ Append(&in_data, sizeof(in_data));
+ return *this;
+}
+
+Packet& Packet::Write(s16 in_data) {
+ s16 toWrite = htons(in_data);
+ Append(&toWrite, sizeof(toWrite));
+ return *this;
+}
+
+Packet& Packet::Write(u16 in_data) {
+ u16 toWrite = htons(in_data);
+ Append(&toWrite, sizeof(toWrite));
+ return *this;
+}
+
+Packet& Packet::Write(s32 in_data) {
+ s32 toWrite = htonl(in_data);
+ Append(&toWrite, sizeof(toWrite));
+ return *this;
+}
+
+Packet& Packet::Write(u32 in_data) {
+ u32 toWrite = htonl(in_data);
+ Append(&toWrite, sizeof(toWrite));
+ return *this;
+}
+
+Packet& Packet::Write(s64 in_data) {
+ s64 toWrite = htonll(in_data);
+ Append(&toWrite, sizeof(toWrite));
+ return *this;
+}
+
+Packet& Packet::Write(u64 in_data) {
+ u64 toWrite = htonll(in_data);
+ Append(&toWrite, sizeof(toWrite));
+ return *this;
+}
+
+Packet& Packet::Write(float in_data) {
+ Append(&in_data, sizeof(in_data));
+ return *this;
+}
+
+Packet& Packet::Write(double in_data) {
+ Append(&in_data, sizeof(in_data));
+ return *this;
+}
+
+Packet& Packet::Write(const char* in_data) {
+ // First insert string length
+ u32 length = static_cast<u32>(std::strlen(in_data));
+ Write(length);
+
+ // Then insert characters
+ Append(in_data, length * sizeof(char));
+
+ return *this;
+}
+
+Packet& Packet::Write(const std::string& in_data) {
+ // First insert string length
+ u32 length = static_cast<u32>(in_data.size());
+ Write(length);
+
+ // Then insert characters
+ if (length > 0)
+ Append(in_data.c_str(), length * sizeof(std::string::value_type));
+
+ return *this;
+}
+
+bool Packet::CheckSize(std::size_t size) {
+ is_valid = is_valid && (read_pos + size <= data.size());
+
+ return is_valid;
+}
+
+} // namespace Network
diff --git a/src/network/packet.h b/src/network/packet.h
new file mode 100644
index 000000000..e69217488
--- /dev/null
+++ b/src/network/packet.h
@@ -0,0 +1,165 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+#include "common/common_types.h"
+
+namespace Network {
+
+/// A class that serializes data for network transfer. It also handles endianess
+class Packet {
+public:
+ Packet() = default;
+ ~Packet() = default;
+
+ /**
+ * Append data to the end of the packet
+ * @param data Pointer to the sequence of bytes to append
+ * @param size_in_bytes Number of bytes to append
+ */
+ void Append(const void* data, std::size_t size_in_bytes);
+
+ /**
+ * Reads data from the current read position of the packet
+ * @param out_data Pointer where the data should get written to
+ * @param size_in_bytes Number of bytes to read
+ */
+ void Read(void* out_data, std::size_t size_in_bytes);
+
+ /**
+ * Clear the packet
+ * After calling Clear, the packet is empty.
+ */
+ void Clear();
+
+ /**
+ * Ignores bytes while reading
+ * @param length THe number of bytes to ignore
+ */
+ void IgnoreBytes(u32 length);
+
+ /**
+ * Get a pointer to the data contained in the packet
+ * @return Pointer to the data
+ */
+ const void* GetData() const;
+
+ /**
+ * This function returns the number of bytes pointed to by
+ * what getData returns.
+ * @return Data size, in bytes
+ */
+ std::size_t GetDataSize() const;
+
+ /**
+ * This function is useful to know if there is some data
+ * left to be read, without actually reading it.
+ * @return True if all data was read, false otherwise
+ */
+ bool EndOfPacket() const;
+
+ explicit operator bool() const;
+
+ /// Overloads of read function to read data from the packet
+ Packet& Read(bool& out_data);
+ Packet& Read(s8& out_data);
+ Packet& Read(u8& out_data);
+ Packet& Read(s16& out_data);
+ Packet& Read(u16& out_data);
+ Packet& Read(s32& out_data);
+ Packet& Read(u32& out_data);
+ Packet& Read(s64& out_data);
+ Packet& Read(u64& out_data);
+ Packet& Read(float& out_data);
+ Packet& Read(double& out_data);
+ Packet& Read(char* out_data);
+ Packet& Read(std::string& out_data);
+ template <typename T>
+ Packet& Read(std::vector<T>& out_data);
+ template <typename T, std::size_t S>
+ Packet& Read(std::array<T, S>& out_data);
+
+ /// Overloads of write function to write data into the packet
+ Packet& Write(bool in_data);
+ Packet& Write(s8 in_data);
+ Packet& Write(u8 in_data);
+ Packet& Write(s16 in_data);
+ Packet& Write(u16 in_data);
+ Packet& Write(s32 in_data);
+ Packet& Write(u32 in_data);
+ Packet& Write(s64 in_data);
+ Packet& Write(u64 in_data);
+ Packet& Write(float in_data);
+ Packet& Write(double in_data);
+ Packet& Write(const char* in_data);
+ Packet& Write(const std::string& in_data);
+ template <typename T>
+ Packet& Write(const std::vector<T>& in_data);
+ template <typename T, std::size_t S>
+ Packet& Write(const std::array<T, S>& data);
+
+private:
+ /**
+ * Check if the packet can extract a given number of bytes
+ * This function updates accordingly the state of the packet.
+ * @param size Size to check
+ * @return True if size bytes can be read from the packet
+ */
+ bool CheckSize(std::size_t size);
+
+ // Member data
+ std::vector<char> data; ///< Data stored in the packet
+ std::size_t read_pos = 0; ///< Current reading position in the packet
+ bool is_valid = true; ///< Reading state of the packet
+};
+
+template <typename T>
+Packet& Packet::Read(std::vector<T>& out_data) {
+ // First extract the size
+ u32 size = 0;
+ Read(size);
+ out_data.resize(size);
+
+ // Then extract the data
+ for (std::size_t i = 0; i < out_data.size(); ++i) {
+ T character;
+ Read(character);
+ out_data[i] = character;
+ }
+ return *this;
+}
+
+template <typename T, std::size_t S>
+Packet& Packet::Read(std::array<T, S>& out_data) {
+ for (std::size_t i = 0; i < out_data.size(); ++i) {
+ T character;
+ Read(character);
+ out_data[i] = character;
+ }
+ return *this;
+}
+
+template <typename T>
+Packet& Packet::Write(const std::vector<T>& in_data) {
+ // First insert the size
+ Write(static_cast<u32>(in_data.size()));
+
+ // Then insert the data
+ for (std::size_t i = 0; i < in_data.size(); ++i) {
+ Write(in_data[i]);
+ }
+ return *this;
+}
+
+template <typename T, std::size_t S>
+Packet& Packet::Write(const std::array<T, S>& in_data) {
+ for (std::size_t i = 0; i < in_data.size(); ++i) {
+ Write(in_data[i]);
+ }
+ return *this;
+}
+
+} // namespace Network
diff --git a/src/network/room.cpp b/src/network/room.cpp
new file mode 100644
index 000000000..dc5dbce7f
--- /dev/null
+++ b/src/network/room.cpp
@@ -0,0 +1,1143 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <atomic>
+#include <iomanip>
+#include <mutex>
+#include <random>
+#include <regex>
+#include <shared_mutex>
+#include <sstream>
+#include <thread>
+#include "common/logging/log.h"
+#include "enet/enet.h"
+#include "network/packet.h"
+#include "network/room.h"
+#include "network/verify_user.h"
+
+namespace Network {
+
+class Room::RoomImpl {
+public:
+ std::mt19937 random_gen; ///< Random number generator. Used for GenerateFakeIPAddress
+
+ ENetHost* server = nullptr; ///< Network interface.
+
+ std::atomic<State> state{State::Closed}; ///< Current state of the room.
+ RoomInformation room_information; ///< Information about this room.
+
+ std::string verify_uid; ///< A GUID which may be used for verfication.
+ mutable std::mutex verify_uid_mutex; ///< Mutex for verify_uid
+
+ std::string password; ///< The password required to connect to this room.
+
+ struct Member {
+ std::string nickname; ///< The nickname of the member.
+ GameInfo game_info; ///< The current game of the member
+ IPv4Address fake_ip; ///< The assigned fake ip address of the member.
+ /// Data of the user, often including authenticated forum username.
+ VerifyUser::UserData user_data;
+ ENetPeer* peer; ///< The remote peer.
+ };
+ using MemberList = std::vector<Member>;
+ MemberList members; ///< Information about the members of this room
+ mutable std::shared_mutex member_mutex; ///< Mutex for locking the members list
+
+ UsernameBanList username_ban_list; ///< List of banned usernames
+ IPBanList ip_ban_list; ///< List of banned IP addresses
+ mutable std::mutex ban_list_mutex; ///< Mutex for the ban lists
+
+ RoomImpl() : random_gen(std::random_device()()) {}
+
+ /// Thread that receives and dispatches network packets
+ std::unique_ptr<std::thread> room_thread;
+
+ /// Verification backend of the room
+ std::unique_ptr<VerifyUser::Backend> verify_backend;
+
+ /// Thread function that will receive and dispatch messages until the room is destroyed.
+ void ServerLoop();
+ void StartLoop();
+
+ /**
+ * Parses and answers a room join request from a client.
+ * Validates the uniqueness of the username and assigns the MAC address
+ * that the client will use for the remainder of the connection.
+ */
+ void HandleJoinRequest(const ENetEvent* event);
+
+ /**
+ * Parses and answers a kick request from a client.
+ * Validates the permissions and that the given user exists and then kicks the member.
+ */
+ void HandleModKickPacket(const ENetEvent* event);
+
+ /**
+ * Parses and answers a ban request from a client.
+ * Validates the permissions and bans the user (by forum username or IP).
+ */
+ void HandleModBanPacket(const ENetEvent* event);
+
+ /**
+ * Parses and answers a unban request from a client.
+ * Validates the permissions and unbans the address.
+ */
+ void HandleModUnbanPacket(const ENetEvent* event);
+
+ /**
+ * Parses and answers a get ban list request from a client.
+ * Validates the permissions and returns the ban list.
+ */
+ void HandleModGetBanListPacket(const ENetEvent* event);
+
+ /**
+ * Returns whether the nickname is valid, ie. isn't already taken by someone else in the room.
+ */
+ bool IsValidNickname(const std::string& nickname) const;
+
+ /**
+ * Returns whether the fake ip address is valid, ie. isn't already taken by someone else in the
+ * room.
+ */
+ bool IsValidFakeIPAddress(const IPv4Address& address) const;
+
+ /**
+ * Returns whether a user has mod permissions.
+ */
+ bool HasModPermission(const ENetPeer* client) const;
+
+ /**
+ * Sends a ID_ROOM_IS_FULL message telling the client that the room is full.
+ */
+ void SendRoomIsFull(ENetPeer* client);
+
+ /**
+ * Sends a ID_ROOM_NAME_COLLISION message telling the client that the name is invalid.
+ */
+ void SendNameCollision(ENetPeer* client);
+
+ /**
+ * Sends a ID_ROOM_IP_COLLISION message telling the client that the IP is invalid.
+ */
+ void SendIPCollision(ENetPeer* client);
+
+ /**
+ * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the version is invalid.
+ */
+ void SendVersionMismatch(ENetPeer* client);
+
+ /**
+ * Sends a ID_ROOM_WRONG_PASSWORD message telling the client that the password is wrong.
+ */
+ void SendWrongPassword(ENetPeer* client);
+
+ /**
+ * Notifies the member that its connection attempt was successful,
+ * and it is now part of the room.
+ */
+ void SendJoinSuccess(ENetPeer* client, IPv4Address fake_ip);
+
+ /**
+ * Notifies the member that its connection attempt was successful,
+ * and it is now part of the room, and it has been granted mod permissions.
+ */
+ void SendJoinSuccessAsMod(ENetPeer* client, IPv4Address fake_ip);
+
+ /**
+ * Sends a IdHostKicked message telling the client that they have been kicked.
+ */
+ void SendUserKicked(ENetPeer* client);
+
+ /**
+ * Sends a IdHostBanned message telling the client that they have been banned.
+ */
+ void SendUserBanned(ENetPeer* client);
+
+ /**
+ * Sends a IdModPermissionDenied message telling the client that they do not have mod
+ * permission.
+ */
+ void SendModPermissionDenied(ENetPeer* client);
+
+ /**
+ * Sends a IdModNoSuchUser message telling the client that the given user could not be found.
+ */
+ void SendModNoSuchUser(ENetPeer* client);
+
+ /**
+ * Sends the ban list in response to a client's request for getting ban list.
+ */
+ void SendModBanListResponse(ENetPeer* client);
+
+ /**
+ * Notifies the members that the room is closed,
+ */
+ void SendCloseMessage();
+
+ /**
+ * Sends a system message to all the connected clients.
+ */
+ void SendStatusMessage(StatusMessageTypes type, const std::string& nickname,
+ const std::string& username, const std::string& ip);
+
+ /**
+ * Sends the information about the room, along with the list of members
+ * to every connected client in the room.
+ * The packet has the structure:
+ * <MessageID>ID_ROOM_INFORMATION
+ * <String> room_name
+ * <String> room_description
+ * <u32> member_slots: The max number of clients allowed in this room
+ * <String> uid
+ * <u16> port
+ * <u32> num_members: the number of currently joined clients
+ * This is followed by the following three values for each member:
+ * <String> nickname of that member
+ * <IPv4Address> fake_ip of that member
+ * <String> game_name of that member
+ */
+ void BroadcastRoomInformation();
+
+ /**
+ * Generates a free MAC address to assign to a new client.
+ * The first 3 bytes are the NintendoOUI 0x00, 0x1F, 0x32
+ */
+ IPv4Address GenerateFakeIPAddress();
+
+ /**
+ * Broadcasts this packet to all members except the sender.
+ * @param event The ENet event containing the data
+ */
+ void HandleProxyPacket(const ENetEvent* event);
+
+ /**
+ * Broadcasts this packet to all members except the sender.
+ * @param event The ENet event containing the data
+ */
+ void HandleLdnPacket(const ENetEvent* event);
+
+ /**
+ * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
+ * @param event The ENet event that was received.
+ */
+ void HandleChatPacket(const ENetEvent* event);
+
+ /**
+ * Extracts the game name from a received ENet packet and broadcasts it.
+ * @param event The ENet event that was received.
+ */
+ void HandleGameInfoPacket(const ENetEvent* event);
+
+ /**
+ * Removes the client from the members list if it was in it and announces the change
+ * to all other clients.
+ */
+ void HandleClientDisconnection(ENetPeer* client);
+};
+
+// RoomImpl
+void Room::RoomImpl::ServerLoop() {
+ while (state != State::Closed) {
+ ENetEvent event;
+ if (enet_host_service(server, &event, 5) > 0) {
+ switch (event.type) {
+ case ENET_EVENT_TYPE_RECEIVE:
+ switch (event.packet->data[0]) {
+ case IdJoinRequest:
+ HandleJoinRequest(&event);
+ break;
+ case IdSetGameInfo:
+ HandleGameInfoPacket(&event);
+ break;
+ case IdProxyPacket:
+ HandleProxyPacket(&event);
+ break;
+ case IdLdnPacket:
+ HandleLdnPacket(&event);
+ break;
+ case IdChatMessage:
+ HandleChatPacket(&event);
+ break;
+ // Moderation
+ case IdModKick:
+ HandleModKickPacket(&event);
+ break;
+ case IdModBan:
+ HandleModBanPacket(&event);
+ break;
+ case IdModUnban:
+ HandleModUnbanPacket(&event);
+ break;
+ case IdModGetBanList:
+ HandleModGetBanListPacket(&event);
+ break;
+ }
+ enet_packet_destroy(event.packet);
+ break;
+ case ENET_EVENT_TYPE_DISCONNECT:
+ HandleClientDisconnection(event.peer);
+ break;
+ case ENET_EVENT_TYPE_NONE:
+ case ENET_EVENT_TYPE_CONNECT:
+ break;
+ }
+ }
+ }
+ // Close the connection to all members:
+ SendCloseMessage();
+}
+
+void Room::RoomImpl::StartLoop() {
+ room_thread = std::make_unique<std::thread>(&Room::RoomImpl::ServerLoop, this);
+}
+
+void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
+ {
+ std::lock_guard lock(member_mutex);
+ if (members.size() >= room_information.member_slots) {
+ SendRoomIsFull(event->peer);
+ return;
+ }
+ }
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+ packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
+ std::string nickname;
+ packet.Read(nickname);
+
+ IPv4Address preferred_fake_ip;
+ packet.Read(preferred_fake_ip);
+
+ u32 client_version;
+ packet.Read(client_version);
+
+ std::string pass;
+ packet.Read(pass);
+
+ std::string token;
+ packet.Read(token);
+
+ if (pass != password) {
+ SendWrongPassword(event->peer);
+ return;
+ }
+
+ if (!IsValidNickname(nickname)) {
+ SendNameCollision(event->peer);
+ return;
+ }
+
+ if (preferred_fake_ip != NoPreferredIP) {
+ // Verify if the preferred fake ip is available
+ if (!IsValidFakeIPAddress(preferred_fake_ip)) {
+ SendIPCollision(event->peer);
+ return;
+ }
+ } else {
+ // Assign a fake ip address of this client automatically
+ preferred_fake_ip = GenerateFakeIPAddress();
+ }
+
+ if (client_version != network_version) {
+ SendVersionMismatch(event->peer);
+ return;
+ }
+
+ // At this point the client is ready to be added to the room.
+ Member member{};
+ member.fake_ip = preferred_fake_ip;
+ member.nickname = nickname;
+ member.peer = event->peer;
+
+ std::string uid;
+ {
+ std::lock_guard lock(verify_uid_mutex);
+ uid = verify_uid;
+ }
+ member.user_data = verify_backend->LoadUserData(uid, token);
+
+ std::string ip;
+ {
+ std::lock_guard lock(ban_list_mutex);
+
+ // Check username ban
+ if (!member.user_data.username.empty() &&
+ std::find(username_ban_list.begin(), username_ban_list.end(),
+ member.user_data.username) != username_ban_list.end()) {
+
+ SendUserBanned(event->peer);
+ return;
+ }
+
+ // Check IP ban
+ std::array<char, 256> ip_raw{};
+ enet_address_get_host_ip(&event->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
+ ip = ip_raw.data();
+
+ if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) != ip_ban_list.end()) {
+ SendUserBanned(event->peer);
+ return;
+ }
+ }
+
+ // Notify everyone that the user has joined.
+ SendStatusMessage(IdMemberJoin, member.nickname, member.user_data.username, ip);
+
+ {
+ std::lock_guard lock(member_mutex);
+ members.push_back(std::move(member));
+ }
+
+ // Notify everyone that the room information has changed.
+ BroadcastRoomInformation();
+ if (HasModPermission(event->peer)) {
+ SendJoinSuccessAsMod(event->peer, preferred_fake_ip);
+ } else {
+ SendJoinSuccess(event->peer, preferred_fake_ip);
+ }
+}
+
+void Room::RoomImpl::HandleModKickPacket(const ENetEvent* event) {
+ if (!HasModPermission(event->peer)) {
+ SendModPermissionDenied(event->peer);
+ return;
+ }
+
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+ packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
+
+ std::string nickname;
+ packet.Read(nickname);
+
+ std::string username, ip;
+ {
+ std::lock_guard lock(member_mutex);
+ const auto target_member =
+ std::find_if(members.begin(), members.end(),
+ [&nickname](const auto& member) { return member.nickname == nickname; });
+ if (target_member == members.end()) {
+ SendModNoSuchUser(event->peer);
+ return;
+ }
+
+ // Notify the kicked member
+ SendUserKicked(target_member->peer);
+
+ username = target_member->user_data.username;
+
+ std::array<char, 256> ip_raw{};
+ enet_address_get_host_ip(&target_member->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
+ ip = ip_raw.data();
+
+ enet_peer_disconnect(target_member->peer, 0);
+ members.erase(target_member);
+ }
+
+ // Announce the change to all clients.
+ SendStatusMessage(IdMemberKicked, nickname, username, ip);
+ BroadcastRoomInformation();
+}
+
+void Room::RoomImpl::HandleModBanPacket(const ENetEvent* event) {
+ if (!HasModPermission(event->peer)) {
+ SendModPermissionDenied(event->peer);
+ return;
+ }
+
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+ packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
+
+ std::string nickname;
+ packet.Read(nickname);
+
+ std::string username, ip;
+ {
+ std::lock_guard lock(member_mutex);
+ const auto target_member =
+ std::find_if(members.begin(), members.end(),
+ [&nickname](const auto& member) { return member.nickname == nickname; });
+ if (target_member == members.end()) {
+ SendModNoSuchUser(event->peer);
+ return;
+ }
+
+ // Notify the banned member
+ SendUserBanned(target_member->peer);
+
+ nickname = target_member->nickname;
+ username = target_member->user_data.username;
+
+ std::array<char, 256> ip_raw{};
+ enet_address_get_host_ip(&target_member->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
+ ip = ip_raw.data();
+
+ enet_peer_disconnect(target_member->peer, 0);
+ members.erase(target_member);
+ }
+
+ {
+ std::lock_guard lock(ban_list_mutex);
+
+ if (!username.empty()) {
+ // Ban the forum username
+ if (std::find(username_ban_list.begin(), username_ban_list.end(), username) ==
+ username_ban_list.end()) {
+
+ username_ban_list.emplace_back(username);
+ }
+ }
+
+ // Ban the member's IP as well
+ if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) == ip_ban_list.end()) {
+ ip_ban_list.emplace_back(ip);
+ }
+ }
+
+ // Announce the change to all clients.
+ SendStatusMessage(IdMemberBanned, nickname, username, ip);
+ BroadcastRoomInformation();
+}
+
+void Room::RoomImpl::HandleModUnbanPacket(const ENetEvent* event) {
+ if (!HasModPermission(event->peer)) {
+ SendModPermissionDenied(event->peer);
+ return;
+ }
+
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+ packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
+
+ std::string address;
+ packet.Read(address);
+
+ bool unbanned = false;
+ {
+ std::lock_guard lock(ban_list_mutex);
+
+ auto it = std::find(username_ban_list.begin(), username_ban_list.end(), address);
+ if (it != username_ban_list.end()) {
+ unbanned = true;
+ username_ban_list.erase(it);
+ }
+
+ it = std::find(ip_ban_list.begin(), ip_ban_list.end(), address);
+ if (it != ip_ban_list.end()) {
+ unbanned = true;
+ ip_ban_list.erase(it);
+ }
+ }
+
+ if (unbanned) {
+ SendStatusMessage(IdAddressUnbanned, address, "", "");
+ } else {
+ SendModNoSuchUser(event->peer);
+ }
+}
+
+void Room::RoomImpl::HandleModGetBanListPacket(const ENetEvent* event) {
+ if (!HasModPermission(event->peer)) {
+ SendModPermissionDenied(event->peer);
+ return;
+ }
+
+ SendModBanListResponse(event->peer);
+}
+
+bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const {
+ // A nickname is valid if it matches the regex and is not already taken by anybody else in the
+ // room.
+ const std::regex nickname_regex("^[ a-zA-Z0-9._-]{4,20}$");
+ if (!std::regex_match(nickname, nickname_regex))
+ return false;
+
+ std::lock_guard lock(member_mutex);
+ return std::all_of(members.begin(), members.end(),
+ [&nickname](const auto& member) { return member.nickname != nickname; });
+}
+
+bool Room::RoomImpl::IsValidFakeIPAddress(const IPv4Address& address) const {
+ // An IP address is valid if it is not already taken by anybody else in the room.
+ std::lock_guard lock(member_mutex);
+ return std::all_of(members.begin(), members.end(),
+ [&address](const auto& member) { return member.fake_ip != address; });
+}
+
+bool Room::RoomImpl::HasModPermission(const ENetPeer* client) const {
+ std::lock_guard lock(member_mutex);
+ const auto sending_member =
+ std::find_if(members.begin(), members.end(),
+ [client](const auto& member) { return member.peer == client; });
+ if (sending_member == members.end()) {
+ return false;
+ }
+ if (room_information.enable_yuzu_mods &&
+ sending_member->user_data.moderator) { // Community moderator
+
+ return true;
+ }
+ if (!room_information.host_username.empty() &&
+ sending_member->user_data.username == room_information.host_username) { // Room host
+
+ return true;
+ }
+ return false;
+}
+
+void Room::RoomImpl::SendNameCollision(ENetPeer* client) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdNameCollision));
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendIPCollision(ENetPeer* client) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdIpCollision));
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendWrongPassword(ENetPeer* client) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdWrongPassword));
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendRoomIsFull(ENetPeer* client) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdRoomIsFull));
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdVersionMismatch));
+ packet.Write(network_version);
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, IPv4Address fake_ip) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdJoinSuccess));
+ packet.Write(fake_ip);
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendJoinSuccessAsMod(ENetPeer* client, IPv4Address fake_ip) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdJoinSuccessAsMod));
+ packet.Write(fake_ip);
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendUserKicked(ENetPeer* client) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdHostKicked));
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendUserBanned(ENetPeer* client) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdHostBanned));
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendModPermissionDenied(ENetPeer* client) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdModPermissionDenied));
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendModNoSuchUser(ENetPeer* client) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdModNoSuchUser));
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendModBanListResponse(ENetPeer* client) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdModBanListResponse));
+ {
+ std::lock_guard lock(ban_list_mutex);
+ packet.Write(username_ban_list);
+ packet.Write(ip_ban_list);
+ }
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendCloseMessage() {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdCloseRoom));
+ std::lock_guard lock(member_mutex);
+ if (!members.empty()) {
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ for (auto& member : members) {
+ enet_peer_send(member.peer, 0, enet_packet);
+ }
+ }
+ enet_host_flush(server);
+ for (auto& member : members) {
+ enet_peer_disconnect(member.peer, 0);
+ }
+}
+
+void Room::RoomImpl::SendStatusMessage(StatusMessageTypes type, const std::string& nickname,
+ const std::string& username, const std::string& ip) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdStatusMessage));
+ packet.Write(static_cast<u8>(type));
+ packet.Write(nickname);
+ packet.Write(username);
+ std::lock_guard lock(member_mutex);
+ if (!members.empty()) {
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ for (auto& member : members) {
+ enet_peer_send(member.peer, 0, enet_packet);
+ }
+ }
+ enet_host_flush(server);
+
+ const std::string display_name =
+ username.empty() ? nickname : fmt::format("{} ({})", nickname, username);
+
+ switch (type) {
+ case IdMemberJoin:
+ LOG_INFO(Network, "[{}] {} has joined.", ip, display_name);
+ break;
+ case IdMemberLeave:
+ LOG_INFO(Network, "[{}] {} has left.", ip, display_name);
+ break;
+ case IdMemberKicked:
+ LOG_INFO(Network, "[{}] {} has been kicked.", ip, display_name);
+ break;
+ case IdMemberBanned:
+ LOG_INFO(Network, "[{}] {} has been banned.", ip, display_name);
+ break;
+ case IdAddressUnbanned:
+ LOG_INFO(Network, "{} has been unbanned.", display_name);
+ break;
+ }
+}
+
+void Room::RoomImpl::BroadcastRoomInformation() {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdRoomInformation));
+ packet.Write(room_information.name);
+ packet.Write(room_information.description);
+ packet.Write(room_information.member_slots);
+ packet.Write(room_information.port);
+ packet.Write(room_information.preferred_game.name);
+ packet.Write(room_information.host_username);
+
+ packet.Write(static_cast<u32>(members.size()));
+ {
+ std::lock_guard lock(member_mutex);
+ for (const auto& member : members) {
+ packet.Write(member.nickname);
+ packet.Write(member.fake_ip);
+ packet.Write(member.game_info.name);
+ packet.Write(member.game_info.id);
+ packet.Write(member.game_info.version);
+ packet.Write(member.user_data.username);
+ packet.Write(member.user_data.display_name);
+ packet.Write(member.user_data.avatar_url);
+ }
+ }
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_host_broadcast(server, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+IPv4Address Room::RoomImpl::GenerateFakeIPAddress() {
+ IPv4Address result_ip{192, 168, 0, 0};
+ std::uniform_int_distribution<> dis(0x01, 0xFE); // Random byte between 1 and 0xFE
+ do {
+ for (std::size_t i = 2; i < result_ip.size(); ++i) {
+ result_ip[i] = dis(random_gen);
+ }
+ } while (!IsValidFakeIPAddress(result_ip));
+
+ return result_ip;
+}
+
+void Room::RoomImpl::HandleProxyPacket(const ENetEvent* event) {
+ Packet in_packet;
+ in_packet.Append(event->packet->data, event->packet->dataLength);
+ in_packet.IgnoreBytes(sizeof(u8)); // Message type
+
+ in_packet.IgnoreBytes(sizeof(u8)); // Domain
+ in_packet.IgnoreBytes(sizeof(IPv4Address)); // IP
+ in_packet.IgnoreBytes(sizeof(u16)); // Port
+
+ in_packet.IgnoreBytes(sizeof(u8)); // Domain
+ IPv4Address remote_ip;
+ in_packet.Read(remote_ip); // IP
+ in_packet.IgnoreBytes(sizeof(u16)); // Port
+
+ in_packet.IgnoreBytes(sizeof(u8)); // Protocol
+
+ bool broadcast;
+ in_packet.Read(broadcast); // Broadcast
+
+ Packet out_packet;
+ out_packet.Append(event->packet->data, event->packet->dataLength);
+ ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
+ ENET_PACKET_FLAG_RELIABLE);
+
+ const auto& destination_address = remote_ip;
+ if (broadcast) { // Send the data to everyone except the sender
+ std::lock_guard lock(member_mutex);
+ bool sent_packet = false;
+ for (const auto& member : members) {
+ if (member.peer != event->peer) {
+ sent_packet = true;
+ enet_peer_send(member.peer, 0, enet_packet);
+ }
+ }
+
+ if (!sent_packet) {
+ enet_packet_destroy(enet_packet);
+ }
+ } else { // Send the data only to the destination client
+ std::lock_guard lock(member_mutex);
+ auto member = std::find_if(members.begin(), members.end(),
+ [destination_address](const Member& member_entry) -> bool {
+ return member_entry.fake_ip == destination_address;
+ });
+ if (member != members.end()) {
+ enet_peer_send(member->peer, 0, enet_packet);
+ } else {
+ LOG_ERROR(Network,
+ "Attempting to send to unknown IP address: "
+ "{}.{}.{}.{}",
+ destination_address[0], destination_address[1], destination_address[2],
+ destination_address[3]);
+ enet_packet_destroy(enet_packet);
+ }
+ }
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::HandleLdnPacket(const ENetEvent* event) {
+ Packet in_packet;
+ in_packet.Append(event->packet->data, event->packet->dataLength);
+
+ in_packet.IgnoreBytes(sizeof(u8)); // Message type
+
+ in_packet.IgnoreBytes(sizeof(u8)); // LAN packet type
+ in_packet.IgnoreBytes(sizeof(IPv4Address)); // Local IP
+
+ IPv4Address remote_ip;
+ in_packet.Read(remote_ip); // Remote IP
+
+ bool broadcast;
+ in_packet.Read(broadcast); // Broadcast
+
+ Packet out_packet;
+ out_packet.Append(event->packet->data, event->packet->dataLength);
+ ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
+ ENET_PACKET_FLAG_RELIABLE);
+
+ const auto& destination_address = remote_ip;
+ if (broadcast) { // Send the data to everyone except the sender
+ std::lock_guard lock(member_mutex);
+ bool sent_packet = false;
+ for (const auto& member : members) {
+ if (member.peer != event->peer) {
+ sent_packet = true;
+ enet_peer_send(member.peer, 0, enet_packet);
+ }
+ }
+
+ if (!sent_packet) {
+ enet_packet_destroy(enet_packet);
+ }
+ } else {
+ std::lock_guard lock(member_mutex);
+ auto member = std::find_if(members.begin(), members.end(),
+ [destination_address](const Member& member_entry) -> bool {
+ return member_entry.fake_ip == destination_address;
+ });
+ if (member != members.end()) {
+ enet_peer_send(member->peer, 0, enet_packet);
+ } else {
+ LOG_ERROR(Network,
+ "Attempting to send to unknown IP address: "
+ "{}.{}.{}.{}",
+ destination_address[0], destination_address[1], destination_address[2],
+ destination_address[3]);
+ enet_packet_destroy(enet_packet);
+ }
+ }
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) {
+ Packet in_packet;
+ in_packet.Append(event->packet->data, event->packet->dataLength);
+
+ in_packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
+ std::string message;
+ in_packet.Read(message);
+ auto CompareNetworkAddress = [event](const Member member) -> bool {
+ return member.peer == event->peer;
+ };
+
+ std::lock_guard lock(member_mutex);
+ const auto sending_member = std::find_if(members.begin(), members.end(), CompareNetworkAddress);
+ if (sending_member == members.end()) {
+ return; // Received a chat message from a unknown sender
+ }
+
+ // Limit the size of chat messages to MaxMessageSize
+ message.resize(std::min(static_cast<u32>(message.size()), MaxMessageSize));
+
+ Packet out_packet;
+ out_packet.Write(static_cast<u8>(IdChatMessage));
+ out_packet.Write(sending_member->nickname);
+ out_packet.Write(sending_member->user_data.username);
+ out_packet.Write(message);
+
+ ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
+ ENET_PACKET_FLAG_RELIABLE);
+ bool sent_packet = false;
+ for (const auto& member : members) {
+ if (member.peer != event->peer) {
+ sent_packet = true;
+ enet_peer_send(member.peer, 0, enet_packet);
+ }
+ }
+
+ if (!sent_packet) {
+ enet_packet_destroy(enet_packet);
+ }
+
+ enet_host_flush(server);
+
+ if (sending_member->user_data.username.empty()) {
+ LOG_INFO(Network, "{}: {}", sending_member->nickname, message);
+ } else {
+ LOG_INFO(Network, "{} ({}): {}", sending_member->nickname,
+ sending_member->user_data.username, message);
+ }
+}
+
+void Room::RoomImpl::HandleGameInfoPacket(const ENetEvent* event) {
+ Packet in_packet;
+ in_packet.Append(event->packet->data, event->packet->dataLength);
+
+ in_packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
+ GameInfo game_info;
+ in_packet.Read(game_info.name);
+ in_packet.Read(game_info.id);
+ in_packet.Read(game_info.version);
+
+ {
+ std::lock_guard lock(member_mutex);
+ auto member = std::find_if(members.begin(), members.end(),
+ [event](const Member& member_entry) -> bool {
+ return member_entry.peer == event->peer;
+ });
+ if (member != members.end()) {
+ member->game_info = game_info;
+
+ const std::string display_name =
+ member->user_data.username.empty()
+ ? member->nickname
+ : fmt::format("{} ({})", member->nickname, member->user_data.username);
+
+ if (game_info.name.empty()) {
+ LOG_INFO(Network, "{} is not playing", display_name);
+ } else {
+ LOG_INFO(Network, "{} is playing {} ({})", display_name, game_info.name,
+ game_info.version);
+ }
+ }
+ }
+ BroadcastRoomInformation();
+}
+
+void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) {
+ // Remove the client from the members list.
+ std::string nickname, username, ip;
+ {
+ std::lock_guard lock(member_mutex);
+ auto member =
+ std::find_if(members.begin(), members.end(), [client](const Member& member_entry) {
+ return member_entry.peer == client;
+ });
+ if (member != members.end()) {
+ nickname = member->nickname;
+ username = member->user_data.username;
+
+ std::array<char, 256> ip_raw{};
+ enet_address_get_host_ip(&member->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
+ ip = ip_raw.data();
+
+ members.erase(member);
+ }
+ }
+
+ // Announce the change to all clients.
+ enet_peer_disconnect(client, 0);
+ if (!nickname.empty())
+ SendStatusMessage(IdMemberLeave, nickname, username, ip);
+ BroadcastRoomInformation();
+}
+
+// Room
+Room::Room() : room_impl{std::make_unique<RoomImpl>()} {}
+
+Room::~Room() = default;
+
+bool Room::Create(const std::string& name, const std::string& description,
+ const std::string& server_address, u16 server_port, const std::string& password,
+ const u32 max_connections, const std::string& host_username,
+ const GameInfo preferred_game,
+ std::unique_ptr<VerifyUser::Backend> verify_backend,
+ const Room::BanList& ban_list, bool enable_yuzu_mods) {
+ ENetAddress address;
+ address.host = ENET_HOST_ANY;
+ if (!server_address.empty()) {
+ enet_address_set_host(&address, server_address.c_str());
+ }
+ address.port = server_port;
+
+ // In order to send the room is full message to the connecting client, we need to leave one
+ // slot open so enet won't reject the incoming connection without telling us
+ room_impl->server = enet_host_create(&address, max_connections + 1, NumChannels, 0, 0);
+ if (!room_impl->server) {
+ return false;
+ }
+ room_impl->state = State::Open;
+
+ room_impl->room_information.name = name;
+ room_impl->room_information.description = description;
+ room_impl->room_information.member_slots = max_connections;
+ room_impl->room_information.port = server_port;
+ room_impl->room_information.preferred_game = preferred_game;
+ room_impl->room_information.host_username = host_username;
+ room_impl->room_information.enable_yuzu_mods = enable_yuzu_mods;
+ room_impl->password = password;
+ room_impl->verify_backend = std::move(verify_backend);
+ room_impl->username_ban_list = ban_list.first;
+ room_impl->ip_ban_list = ban_list.second;
+
+ room_impl->StartLoop();
+ return true;
+}
+
+Room::State Room::GetState() const {
+ return room_impl->state;
+}
+
+const RoomInformation& Room::GetRoomInformation() const {
+ return room_impl->room_information;
+}
+
+std::string Room::GetVerifyUID() const {
+ std::lock_guard lock(room_impl->verify_uid_mutex);
+ return room_impl->verify_uid;
+}
+
+Room::BanList Room::GetBanList() const {
+ std::lock_guard lock(room_impl->ban_list_mutex);
+ return {room_impl->username_ban_list, room_impl->ip_ban_list};
+}
+
+std::vector<Member> Room::GetRoomMemberList() const {
+ std::vector<Member> member_list;
+ std::lock_guard lock(room_impl->member_mutex);
+ for (const auto& member_impl : room_impl->members) {
+ Member member;
+ member.nickname = member_impl.nickname;
+ member.username = member_impl.user_data.username;
+ member.display_name = member_impl.user_data.display_name;
+ member.avatar_url = member_impl.user_data.avatar_url;
+ member.fake_ip = member_impl.fake_ip;
+ member.game = member_impl.game_info;
+ member_list.push_back(member);
+ }
+ return member_list;
+}
+
+bool Room::HasPassword() const {
+ return !room_impl->password.empty();
+}
+
+void Room::SetVerifyUID(const std::string& uid) {
+ std::lock_guard lock(room_impl->verify_uid_mutex);
+ room_impl->verify_uid = uid;
+}
+
+void Room::Destroy() {
+ room_impl->state = State::Closed;
+ room_impl->room_thread->join();
+ room_impl->room_thread.reset();
+
+ if (room_impl->server) {
+ enet_host_destroy(room_impl->server);
+ }
+ room_impl->room_information = {};
+ room_impl->server = nullptr;
+ {
+ std::lock_guard lock(room_impl->member_mutex);
+ room_impl->members.clear();
+ }
+ room_impl->room_information.member_slots = 0;
+ room_impl->room_information.name.clear();
+}
+
+} // namespace Network
diff --git a/src/network/room.h b/src/network/room.h
new file mode 100644
index 000000000..edbd3ecfb
--- /dev/null
+++ b/src/network/room.h
@@ -0,0 +1,148 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <string>
+#include <vector>
+#include "common/announce_multiplayer_room.h"
+#include "common/common_types.h"
+#include "common/socket_types.h"
+#include "network/verify_user.h"
+
+namespace Network {
+
+using AnnounceMultiplayerRoom::GameInfo;
+using AnnounceMultiplayerRoom::Member;
+using AnnounceMultiplayerRoom::RoomInformation;
+
+constexpr u32 network_version = 1; ///< The version of this Room and RoomMember
+
+constexpr u16 DefaultRoomPort = 24872;
+
+constexpr u32 MaxMessageSize = 500;
+
+/// Maximum number of concurrent connections allowed to this room.
+static constexpr u32 MaxConcurrentConnections = 254;
+
+constexpr std::size_t NumChannels = 1; // Number of channels used for the connection
+
+/// A special IP address that tells the room we're joining to assign us a IP address
+/// automatically.
+constexpr IPv4Address NoPreferredIP = {0xFF, 0xFF, 0xFF, 0xFF};
+
+// The different types of messages that can be sent. The first byte of each packet defines the type
+enum RoomMessageTypes : u8 {
+ IdJoinRequest = 1,
+ IdJoinSuccess,
+ IdRoomInformation,
+ IdSetGameInfo,
+ IdProxyPacket,
+ IdLdnPacket,
+ IdChatMessage,
+ IdNameCollision,
+ IdIpCollision,
+ IdVersionMismatch,
+ IdWrongPassword,
+ IdCloseRoom,
+ IdRoomIsFull,
+ IdStatusMessage,
+ IdHostKicked,
+ IdHostBanned,
+ /// Moderation requests
+ IdModKick,
+ IdModBan,
+ IdModUnban,
+ IdModGetBanList,
+ // Moderation responses
+ IdModBanListResponse,
+ IdModPermissionDenied,
+ IdModNoSuchUser,
+ IdJoinSuccessAsMod,
+};
+
+/// Types of system status messages
+enum StatusMessageTypes : u8 {
+ IdMemberJoin = 1, ///< Member joining
+ IdMemberLeave, ///< Member leaving
+ IdMemberKicked, ///< A member is kicked from the room
+ IdMemberBanned, ///< A member is banned from the room
+ IdAddressUnbanned, ///< A username / ip address is unbanned from the room
+};
+
+/// This is what a server [person creating a server] would use.
+class Room final {
+public:
+ enum class State : u8 {
+ Open, ///< The room is open and ready to accept connections.
+ Closed, ///< The room is not opened and can not accept connections.
+ };
+
+ Room();
+ ~Room();
+
+ /**
+ * Gets the current state of the room.
+ */
+ State GetState() const;
+
+ /**
+ * Gets the room information of the room.
+ */
+ const RoomInformation& GetRoomInformation() const;
+
+ /**
+ * Gets the verify UID of this room.
+ */
+ std::string GetVerifyUID() const;
+
+ /**
+ * Gets a list of the mbmers connected to the room.
+ */
+ std::vector<Member> GetRoomMemberList() const;
+
+ /**
+ * Checks if the room is password protected
+ */
+ bool HasPassword() const;
+
+ using UsernameBanList = std::vector<std::string>;
+ using IPBanList = std::vector<std::string>;
+
+ using BanList = std::pair<UsernameBanList, IPBanList>;
+
+ /**
+ * Creates the socket for this room. Will bind to default address if
+ * server is empty string.
+ */
+ bool Create(const std::string& name, const std::string& description = "",
+ const std::string& server = "", u16 server_port = DefaultRoomPort,
+ const std::string& password = "",
+ const u32 max_connections = MaxConcurrentConnections,
+ const std::string& host_username = "", const GameInfo = {},
+ std::unique_ptr<VerifyUser::Backend> verify_backend = nullptr,
+ const BanList& ban_list = {}, bool enable_yuzu_mods = false);
+
+ /**
+ * Sets the verification GUID of the room.
+ */
+ void SetVerifyUID(const std::string& uid);
+
+ /**
+ * Gets the ban list (including banned forum usernames and IPs) of the room.
+ */
+ BanList GetBanList() const;
+
+ /**
+ * Destroys the socket
+ */
+ void Destroy();
+
+private:
+ class RoomImpl;
+ std::unique_ptr<RoomImpl> room_impl;
+};
+
+} // namespace Network
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
new file mode 100644
index 000000000..b94cb24ad
--- /dev/null
+++ b/src/network/room_member.cpp
@@ -0,0 +1,766 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <atomic>
+#include <list>
+#include <mutex>
+#include <set>
+#include <thread>
+#include "common/assert.h"
+#include "common/socket_types.h"
+#include "enet/enet.h"
+#include "network/packet.h"
+#include "network/room_member.h"
+
+namespace Network {
+
+constexpr u32 ConnectionTimeoutMs = 5000;
+
+class RoomMember::RoomMemberImpl {
+public:
+ ENetHost* client = nullptr; ///< ENet network interface.
+ ENetPeer* server = nullptr; ///< The server peer the client is connected to
+
+ /// Information about the clients connected to the same room as us.
+ MemberList member_information;
+ /// Information about the room we're connected to.
+ RoomInformation room_information;
+
+ /// The current game name, id and version
+ GameInfo current_game_info;
+
+ std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
+ void SetState(const State new_state);
+ void SetError(const Error new_error);
+ bool IsConnected() const;
+
+ std::string nickname; ///< The nickname of this member.
+
+ std::string username; ///< The username of this member.
+ mutable std::mutex username_mutex; ///< Mutex for locking username.
+
+ IPv4Address fake_ip; ///< The fake ip of this member.
+
+ std::mutex network_mutex; ///< Mutex that controls access to the `client` variable.
+ /// Thread that receives and dispatches network packets
+ std::unique_ptr<std::thread> loop_thread;
+ std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable.
+ std::list<Packet> send_list; ///< A list that stores all packets to send the async
+
+ template <typename T>
+ using CallbackSet = std::set<CallbackHandle<T>>;
+ std::mutex callback_mutex; ///< The mutex used for handling callbacks
+
+ class Callbacks {
+ public:
+ template <typename T>
+ CallbackSet<T>& Get();
+
+ private:
+ CallbackSet<ProxyPacket> callback_set_proxy_packet;
+ CallbackSet<LDNPacket> callback_set_ldn_packet;
+ CallbackSet<ChatEntry> callback_set_chat_messages;
+ CallbackSet<StatusMessageEntry> callback_set_status_messages;
+ CallbackSet<RoomInformation> callback_set_room_information;
+ CallbackSet<State> callback_set_state;
+ CallbackSet<Error> callback_set_error;
+ CallbackSet<Room::BanList> callback_set_ban_list;
+ };
+ Callbacks callbacks; ///< All CallbackSets to all events
+
+ void MemberLoop();
+
+ void StartLoop();
+
+ /**
+ * Sends data to the room. It will be send on channel 0 with flag RELIABLE
+ * @param packet The data to send
+ */
+ void Send(Packet&& packet);
+
+ /**
+ * Sends a request to the server, asking for permission to join a room with the specified
+ * nickname and preferred fake ip.
+ * @params nickname The desired nickname.
+ * @params preferred_fake_ip The preferred IP address to use in the room, the NoPreferredIP
+ * tells
+ * @params password The password for the room
+ * the server to assign one for us.
+ */
+ void SendJoinRequest(const std::string& nickname_,
+ const IPv4Address& preferred_fake_ip = NoPreferredIP,
+ const std::string& password = "", const std::string& token = "");
+
+ /**
+ * Extracts a MAC Address from a received ENet packet.
+ * @param event The ENet event that was received.
+ */
+ void HandleJoinPacket(const ENetEvent* event);
+ /**
+ * Extracts RoomInformation and MemberInformation from a received ENet packet.
+ * @param event The ENet event that was received.
+ */
+ void HandleRoomInformationPacket(const ENetEvent* event);
+
+ /**
+ * Extracts a ProxyPacket from a received ENet packet.
+ * @param event The ENet event that was received.
+ */
+ void HandleProxyPackets(const ENetEvent* event);
+
+ /**
+ * Extracts an LdnPacket from a received ENet packet.
+ * @param event The ENet event that was received.
+ */
+ void HandleLdnPackets(const ENetEvent* event);
+
+ /**
+ * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
+ * @param event The ENet event that was received.
+ */
+ void HandleChatPacket(const ENetEvent* event);
+
+ /**
+ * Extracts a system message entry from a received ENet packet and adds it to the system message
+ * queue.
+ * @param event The ENet event that was received.
+ */
+ void HandleStatusMessagePacket(const ENetEvent* event);
+
+ /**
+ * Extracts a ban list request response from a received ENet packet.
+ * @param event The ENet event that was received.
+ */
+ void HandleModBanListResponsePacket(const ENetEvent* event);
+
+ /**
+ * Disconnects the RoomMember from the Room
+ */
+ void Disconnect();
+
+ template <typename T>
+ void Invoke(const T& data);
+
+ template <typename T>
+ CallbackHandle<T> Bind(std::function<void(const T&)> callback);
+};
+
+// RoomMemberImpl
+void RoomMember::RoomMemberImpl::SetState(const State new_state) {
+ if (state != new_state) {
+ state = new_state;
+ Invoke<State>(state);
+ }
+}
+
+void RoomMember::RoomMemberImpl::SetError(const Error new_error) {
+ Invoke<Error>(new_error);
+}
+
+bool RoomMember::RoomMemberImpl::IsConnected() const {
+ return state == State::Joining || state == State::Joined || state == State::Moderator;
+}
+
+void RoomMember::RoomMemberImpl::MemberLoop() {
+ // Receive packets while the connection is open
+ while (IsConnected()) {
+ std::lock_guard lock(network_mutex);
+ ENetEvent event;
+ if (enet_host_service(client, &event, 5) > 0) {
+ switch (event.type) {
+ case ENET_EVENT_TYPE_RECEIVE:
+ switch (event.packet->data[0]) {
+ case IdProxyPacket:
+ HandleProxyPackets(&event);
+ break;
+ case IdLdnPacket:
+ HandleLdnPackets(&event);
+ break;
+ case IdChatMessage:
+ HandleChatPacket(&event);
+ break;
+ case IdStatusMessage:
+ HandleStatusMessagePacket(&event);
+ break;
+ case IdRoomInformation:
+ HandleRoomInformationPacket(&event);
+ break;
+ case IdJoinSuccess:
+ case IdJoinSuccessAsMod:
+ // The join request was successful, we are now in the room.
+ // If we joined successfully, there must be at least one client in the room: us.
+ ASSERT_MSG(member_information.size() > 0,
+ "We have not yet received member information.");
+ HandleJoinPacket(&event); // Get the MAC Address for the client
+ if (event.packet->data[0] == IdJoinSuccessAsMod) {
+ SetState(State::Moderator);
+ } else {
+ SetState(State::Joined);
+ }
+ break;
+ case IdModBanListResponse:
+ HandleModBanListResponsePacket(&event);
+ break;
+ case IdRoomIsFull:
+ SetState(State::Idle);
+ SetError(Error::RoomIsFull);
+ break;
+ case IdNameCollision:
+ SetState(State::Idle);
+ SetError(Error::NameCollision);
+ break;
+ case IdIpCollision:
+ SetState(State::Idle);
+ SetError(Error::IpCollision);
+ break;
+ case IdVersionMismatch:
+ SetState(State::Idle);
+ SetError(Error::WrongVersion);
+ break;
+ case IdWrongPassword:
+ SetState(State::Idle);
+ SetError(Error::WrongPassword);
+ break;
+ case IdCloseRoom:
+ SetState(State::Idle);
+ SetError(Error::LostConnection);
+ break;
+ case IdHostKicked:
+ SetState(State::Idle);
+ SetError(Error::HostKicked);
+ break;
+ case IdHostBanned:
+ SetState(State::Idle);
+ SetError(Error::HostBanned);
+ break;
+ case IdModPermissionDenied:
+ SetError(Error::PermissionDenied);
+ break;
+ case IdModNoSuchUser:
+ SetError(Error::NoSuchUser);
+ break;
+ }
+ enet_packet_destroy(event.packet);
+ break;
+ case ENET_EVENT_TYPE_DISCONNECT:
+ if (state == State::Joined || state == State::Moderator) {
+ SetState(State::Idle);
+ SetError(Error::LostConnection);
+ }
+ break;
+ case ENET_EVENT_TYPE_NONE:
+ break;
+ case ENET_EVENT_TYPE_CONNECT:
+ // The ENET_EVENT_TYPE_CONNECT event can not possibly happen here because we're
+ // already connected
+ ASSERT_MSG(false, "Received unexpected connect event while already connected");
+ break;
+ }
+ }
+ std::list<Packet> packets;
+ {
+ std::lock_guard send_lock(send_list_mutex);
+ packets.swap(send_list);
+ }
+ for (const auto& packet : packets) {
+ ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(),
+ ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(server, 0, enetPacket);
+ }
+ enet_host_flush(client);
+ }
+ Disconnect();
+};
+
+void RoomMember::RoomMemberImpl::StartLoop() {
+ loop_thread = std::make_unique<std::thread>(&RoomMember::RoomMemberImpl::MemberLoop, this);
+}
+
+void RoomMember::RoomMemberImpl::Send(Packet&& packet) {
+ std::lock_guard lock(send_list_mutex);
+ send_list.push_back(std::move(packet));
+}
+
+void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname_,
+ const IPv4Address& preferred_fake_ip,
+ const std::string& password,
+ const std::string& token) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdJoinRequest));
+ packet.Write(nickname_);
+ packet.Write(preferred_fake_ip);
+ packet.Write(network_version);
+ packet.Write(password);
+ packet.Write(token);
+ Send(std::move(packet));
+}
+
+void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* event) {
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+
+ // Ignore the first byte, which is the message id.
+ packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
+
+ RoomInformation info{};
+ packet.Read(info.name);
+ packet.Read(info.description);
+ packet.Read(info.member_slots);
+ packet.Read(info.port);
+ packet.Read(info.preferred_game.name);
+ packet.Read(info.host_username);
+ room_information.name = info.name;
+ room_information.description = info.description;
+ room_information.member_slots = info.member_slots;
+ room_information.port = info.port;
+ room_information.preferred_game = info.preferred_game;
+ room_information.host_username = info.host_username;
+
+ u32 num_members;
+ packet.Read(num_members);
+ member_information.resize(num_members);
+
+ for (auto& member : member_information) {
+ packet.Read(member.nickname);
+ packet.Read(member.fake_ip);
+ packet.Read(member.game_info.name);
+ packet.Read(member.game_info.id);
+ packet.Read(member.game_info.version);
+ packet.Read(member.username);
+ packet.Read(member.display_name);
+ packet.Read(member.avatar_url);
+
+ {
+ std::lock_guard lock(username_mutex);
+ if (member.nickname == nickname) {
+ username = member.username;
+ }
+ }
+ }
+ Invoke(room_information);
+}
+
+void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) {
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+
+ // Ignore the first byte, which is the message id.
+ packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
+
+ // Parse the MAC Address from the packet
+ packet.Read(fake_ip);
+}
+
+void RoomMember::RoomMemberImpl::HandleProxyPackets(const ENetEvent* event) {
+ ProxyPacket proxy_packet{};
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+
+ // Ignore the first byte, which is the message id.
+ packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
+
+ // Parse the ProxyPacket from the packet
+ u8 local_family;
+ packet.Read(local_family);
+ proxy_packet.local_endpoint.family = static_cast<Domain>(local_family);
+ packet.Read(proxy_packet.local_endpoint.ip);
+ packet.Read(proxy_packet.local_endpoint.portno);
+
+ u8 remote_family;
+ packet.Read(remote_family);
+ proxy_packet.remote_endpoint.family = static_cast<Domain>(remote_family);
+ packet.Read(proxy_packet.remote_endpoint.ip);
+ packet.Read(proxy_packet.remote_endpoint.portno);
+
+ u8 protocol_type;
+ packet.Read(protocol_type);
+ proxy_packet.protocol = static_cast<Protocol>(protocol_type);
+
+ packet.Read(proxy_packet.broadcast);
+ packet.Read(proxy_packet.data);
+
+ Invoke<ProxyPacket>(proxy_packet);
+}
+
+void RoomMember::RoomMemberImpl::HandleLdnPackets(const ENetEvent* event) {
+ LDNPacket ldn_packet{};
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+
+ // Ignore the first byte, which is the message id.
+ packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
+
+ u8 packet_type;
+ packet.Read(packet_type);
+ ldn_packet.type = static_cast<LDNPacketType>(packet_type);
+
+ packet.Read(ldn_packet.local_ip);
+ packet.Read(ldn_packet.remote_ip);
+ packet.Read(ldn_packet.broadcast);
+
+ packet.Read(ldn_packet.data);
+
+ Invoke<LDNPacket>(ldn_packet);
+}
+
+void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+
+ // Ignore the first byte, which is the message id.
+ packet.IgnoreBytes(sizeof(u8));
+
+ ChatEntry chat_entry{};
+ packet.Read(chat_entry.nickname);
+ packet.Read(chat_entry.username);
+ packet.Read(chat_entry.message);
+ Invoke<ChatEntry>(chat_entry);
+}
+
+void RoomMember::RoomMemberImpl::HandleStatusMessagePacket(const ENetEvent* event) {
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+
+ // Ignore the first byte, which is the message id.
+ packet.IgnoreBytes(sizeof(u8));
+
+ StatusMessageEntry status_message_entry{};
+ u8 type{};
+ packet.Read(type);
+ status_message_entry.type = static_cast<StatusMessageTypes>(type);
+ packet.Read(status_message_entry.nickname);
+ packet.Read(status_message_entry.username);
+ Invoke<StatusMessageEntry>(status_message_entry);
+}
+
+void RoomMember::RoomMemberImpl::HandleModBanListResponsePacket(const ENetEvent* event) {
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+
+ // Ignore the first byte, which is the message id.
+ packet.IgnoreBytes(sizeof(u8));
+
+ Room::BanList ban_list = {};
+ packet.Read(ban_list.first);
+ packet.Read(ban_list.second);
+ Invoke<Room::BanList>(ban_list);
+}
+
+void RoomMember::RoomMemberImpl::Disconnect() {
+ member_information.clear();
+ room_information.member_slots = 0;
+ room_information.name.clear();
+
+ if (!server) {
+ return;
+ }
+ enet_peer_disconnect(server, 0);
+
+ ENetEvent event;
+ while (enet_host_service(client, &event, ConnectionTimeoutMs) > 0) {
+ switch (event.type) {
+ case ENET_EVENT_TYPE_RECEIVE:
+ enet_packet_destroy(event.packet); // Ignore all incoming data
+ break;
+ case ENET_EVENT_TYPE_DISCONNECT:
+ server = nullptr;
+ return;
+ case ENET_EVENT_TYPE_NONE:
+ case ENET_EVENT_TYPE_CONNECT:
+ break;
+ }
+ }
+ // didn't disconnect gracefully force disconnect
+ enet_peer_reset(server);
+ server = nullptr;
+}
+
+template <>
+RoomMember::RoomMemberImpl::CallbackSet<ProxyPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
+ return callback_set_proxy_packet;
+}
+
+template <>
+RoomMember::RoomMemberImpl::CallbackSet<LDNPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
+ return callback_set_ldn_packet;
+}
+
+template <>
+RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>&
+RoomMember::RoomMemberImpl::Callbacks::Get() {
+ return callback_set_state;
+}
+
+template <>
+RoomMember::RoomMemberImpl::CallbackSet<RoomMember::Error>&
+RoomMember::RoomMemberImpl::Callbacks::Get() {
+ return callback_set_error;
+}
+
+template <>
+RoomMember::RoomMemberImpl::CallbackSet<RoomInformation>&
+RoomMember::RoomMemberImpl::Callbacks::Get() {
+ return callback_set_room_information;
+}
+
+template <>
+RoomMember::RoomMemberImpl::CallbackSet<ChatEntry>& RoomMember::RoomMemberImpl::Callbacks::Get() {
+ return callback_set_chat_messages;
+}
+
+template <>
+RoomMember::RoomMemberImpl::CallbackSet<StatusMessageEntry>&
+RoomMember::RoomMemberImpl::Callbacks::Get() {
+ return callback_set_status_messages;
+}
+
+template <>
+RoomMember::RoomMemberImpl::CallbackSet<Room::BanList>&
+RoomMember::RoomMemberImpl::Callbacks::Get() {
+ return callback_set_ban_list;
+}
+
+template <typename T>
+void RoomMember::RoomMemberImpl::Invoke(const T& data) {
+ std::lock_guard lock(callback_mutex);
+ CallbackSet<T> callback_set = callbacks.Get<T>();
+ for (auto const& callback : callback_set) {
+ (*callback)(data);
+ }
+}
+
+template <typename T>
+RoomMember::CallbackHandle<T> RoomMember::RoomMemberImpl::Bind(
+ std::function<void(const T&)> callback) {
+ std::lock_guard lock(callback_mutex);
+ CallbackHandle<T> handle;
+ handle = std::make_shared<std::function<void(const T&)>>(callback);
+ callbacks.Get<T>().insert(handle);
+ return handle;
+}
+
+// RoomMember
+RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} {}
+
+RoomMember::~RoomMember() {
+ ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected");
+ if (room_member_impl->loop_thread) {
+ Leave();
+ }
+}
+
+RoomMember::State RoomMember::GetState() const {
+ return room_member_impl->state;
+}
+
+const RoomMember::MemberList& RoomMember::GetMemberInformation() const {
+ return room_member_impl->member_information;
+}
+
+const std::string& RoomMember::GetNickname() const {
+ return room_member_impl->nickname;
+}
+
+const std::string& RoomMember::GetUsername() const {
+ std::lock_guard lock(room_member_impl->username_mutex);
+ return room_member_impl->username;
+}
+
+const IPv4Address& RoomMember::GetFakeIpAddress() const {
+ ASSERT_MSG(IsConnected(), "Tried to get fake ip address while not connected");
+ return room_member_impl->fake_ip;
+}
+
+RoomInformation RoomMember::GetRoomInformation() const {
+ return room_member_impl->room_information;
+}
+
+void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port,
+ u16 client_port, const IPv4Address& preferred_fake_ip,
+ const std::string& password, const std::string& token) {
+ // If the member is connected, kill the connection first
+ if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) {
+ Leave();
+ }
+ // If the thread isn't running but the ptr still exists, reset it
+ else if (room_member_impl->loop_thread) {
+ room_member_impl->loop_thread.reset();
+ }
+
+ if (!room_member_impl->client) {
+ room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0);
+ ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client");
+ }
+
+ room_member_impl->SetState(State::Joining);
+
+ ENetAddress address{};
+ enet_address_set_host(&address, server_addr);
+ address.port = server_port;
+ room_member_impl->server =
+ enet_host_connect(room_member_impl->client, &address, NumChannels, 0);
+
+ if (!room_member_impl->server) {
+ room_member_impl->SetState(State::Idle);
+ room_member_impl->SetError(Error::UnknownError);
+ return;
+ }
+
+ ENetEvent event{};
+ int net = enet_host_service(room_member_impl->client, &event, ConnectionTimeoutMs);
+ if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
+ room_member_impl->nickname = nick;
+ room_member_impl->StartLoop();
+ room_member_impl->SendJoinRequest(nick, preferred_fake_ip, password, token);
+ SendGameInfo(room_member_impl->current_game_info);
+ } else {
+ enet_peer_disconnect(room_member_impl->server, 0);
+ room_member_impl->SetState(State::Idle);
+ room_member_impl->SetError(Error::CouldNotConnect);
+ }
+}
+
+bool RoomMember::IsConnected() const {
+ return room_member_impl->IsConnected();
+}
+
+void RoomMember::SendProxyPacket(const ProxyPacket& proxy_packet) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdProxyPacket));
+
+ packet.Write(static_cast<u8>(proxy_packet.local_endpoint.family));
+ packet.Write(proxy_packet.local_endpoint.ip);
+ packet.Write(proxy_packet.local_endpoint.portno);
+
+ packet.Write(static_cast<u8>(proxy_packet.remote_endpoint.family));
+ packet.Write(proxy_packet.remote_endpoint.ip);
+ packet.Write(proxy_packet.remote_endpoint.portno);
+
+ packet.Write(static_cast<u8>(proxy_packet.protocol));
+ packet.Write(proxy_packet.broadcast);
+ packet.Write(proxy_packet.data);
+
+ room_member_impl->Send(std::move(packet));
+}
+
+void RoomMember::SendLdnPacket(const LDNPacket& ldn_packet) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdLdnPacket));
+
+ packet.Write(static_cast<u8>(ldn_packet.type));
+
+ packet.Write(ldn_packet.local_ip);
+ packet.Write(ldn_packet.remote_ip);
+ packet.Write(ldn_packet.broadcast);
+
+ packet.Write(ldn_packet.data);
+
+ room_member_impl->Send(std::move(packet));
+}
+
+void RoomMember::SendChatMessage(const std::string& message) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdChatMessage));
+ packet.Write(message);
+ room_member_impl->Send(std::move(packet));
+}
+
+void RoomMember::SendGameInfo(const GameInfo& game_info) {
+ room_member_impl->current_game_info = game_info;
+ if (!IsConnected())
+ return;
+
+ Packet packet;
+ packet.Write(static_cast<u8>(IdSetGameInfo));
+ packet.Write(game_info.name);
+ packet.Write(game_info.id);
+ packet.Write(game_info.version);
+ room_member_impl->Send(std::move(packet));
+}
+
+void RoomMember::SendModerationRequest(RoomMessageTypes type, const std::string& nickname) {
+ ASSERT_MSG(type == IdModKick || type == IdModBan || type == IdModUnban,
+ "type is not a moderation request");
+ if (!IsConnected())
+ return;
+
+ Packet packet;
+ packet.Write(static_cast<u8>(type));
+ packet.Write(nickname);
+ room_member_impl->Send(std::move(packet));
+}
+
+void RoomMember::RequestBanList() {
+ if (!IsConnected())
+ return;
+
+ Packet packet;
+ packet.Write(static_cast<u8>(IdModGetBanList));
+ room_member_impl->Send(std::move(packet));
+}
+
+RoomMember::CallbackHandle<RoomMember::State> RoomMember::BindOnStateChanged(
+ std::function<void(const RoomMember::State&)> callback) {
+ return room_member_impl->Bind(callback);
+}
+
+RoomMember::CallbackHandle<RoomMember::Error> RoomMember::BindOnError(
+ std::function<void(const RoomMember::Error&)> callback) {
+ return room_member_impl->Bind(callback);
+}
+
+RoomMember::CallbackHandle<ProxyPacket> RoomMember::BindOnProxyPacketReceived(
+ std::function<void(const ProxyPacket&)> callback) {
+ return room_member_impl->Bind(callback);
+}
+
+RoomMember::CallbackHandle<LDNPacket> RoomMember::BindOnLdnPacketReceived(
+ std::function<void(const LDNPacket&)> callback) {
+ return room_member_impl->Bind(std::move(callback));
+}
+
+RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged(
+ std::function<void(const RoomInformation&)> callback) {
+ return room_member_impl->Bind(callback);
+}
+
+RoomMember::CallbackHandle<ChatEntry> RoomMember::BindOnChatMessageRecieved(
+ std::function<void(const ChatEntry&)> callback) {
+ return room_member_impl->Bind(callback);
+}
+
+RoomMember::CallbackHandle<StatusMessageEntry> RoomMember::BindOnStatusMessageReceived(
+ std::function<void(const StatusMessageEntry&)> callback) {
+ return room_member_impl->Bind(callback);
+}
+
+RoomMember::CallbackHandle<Room::BanList> RoomMember::BindOnBanListReceived(
+ std::function<void(const Room::BanList&)> callback) {
+ return room_member_impl->Bind(callback);
+}
+
+template <typename T>
+void RoomMember::Unbind(CallbackHandle<T> handle) {
+ std::lock_guard lock(room_member_impl->callback_mutex);
+ room_member_impl->callbacks.Get<T>().erase(handle);
+}
+
+void RoomMember::Leave() {
+ room_member_impl->SetState(State::Idle);
+ room_member_impl->loop_thread->join();
+ room_member_impl->loop_thread.reset();
+
+ enet_host_destroy(room_member_impl->client);
+ room_member_impl->client = nullptr;
+}
+
+template void RoomMember::Unbind(CallbackHandle<ProxyPacket>);
+template void RoomMember::Unbind(CallbackHandle<LDNPacket>);
+template void RoomMember::Unbind(CallbackHandle<RoomMember::State>);
+template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>);
+template void RoomMember::Unbind(CallbackHandle<RoomInformation>);
+template void RoomMember::Unbind(CallbackHandle<ChatEntry>);
+template void RoomMember::Unbind(CallbackHandle<StatusMessageEntry>);
+template void RoomMember::Unbind(CallbackHandle<Room::BanList>);
+
+} // namespace Network
diff --git a/src/network/room_member.h b/src/network/room_member.h
new file mode 100644
index 000000000..0d6417294
--- /dev/null
+++ b/src/network/room_member.h
@@ -0,0 +1,337 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+#include "common/announce_multiplayer_room.h"
+#include "common/common_types.h"
+#include "common/socket_types.h"
+#include "network/room.h"
+
+namespace Network {
+
+using AnnounceMultiplayerRoom::GameInfo;
+using AnnounceMultiplayerRoom::RoomInformation;
+
+enum class LDNPacketType : u8 {
+ Scan,
+ ScanResp,
+ Connect,
+ SyncNetwork,
+ Disconnect,
+ DestroyNetwork,
+};
+
+struct LDNPacket {
+ LDNPacketType type;
+ IPv4Address local_ip;
+ IPv4Address remote_ip;
+ bool broadcast;
+ std::vector<u8> data;
+};
+
+/// Information about the received proxy packets.
+struct ProxyPacket {
+ SockAddrIn local_endpoint;
+ SockAddrIn remote_endpoint;
+ Protocol protocol;
+ bool broadcast;
+ std::vector<u8> data;
+};
+
+/// Represents a chat message.
+struct ChatEntry {
+ std::string nickname; ///< Nickname of the client who sent this message.
+ /// Web services username of the client who sent this message, can be empty.
+ std::string username;
+ std::string message; ///< Body of the message.
+};
+
+/// Represents a system status message.
+struct StatusMessageEntry {
+ StatusMessageTypes type; ///< Type of the message
+ /// Subject of the message. i.e. the user who is joining/leaving/being banned, etc.
+ std::string nickname;
+ std::string username;
+};
+
+/**
+ * This is what a client [person joining a server] would use.
+ * It also has to be used if you host a game yourself (You'd create both, a Room and a
+ * RoomMembership for yourself)
+ */
+class RoomMember final {
+public:
+ enum class State : u8 {
+ Uninitialized, ///< Not initialized
+ Idle, ///< Default state (i.e. not connected)
+ Joining, ///< The client is attempting to join a room.
+ Joined, ///< The client is connected to the room and is ready to send/receive packets.
+ Moderator, ///< The client is connnected to the room and is granted mod permissions.
+ };
+
+ enum class Error : u8 {
+ // Reasons why connection was closed
+ LostConnection, ///< Connection closed
+ HostKicked, ///< Kicked by the host
+
+ // Reasons why connection was rejected
+ UnknownError, ///< Some error [permissions to network device missing or something]
+ NameCollision, ///< Somebody is already using this name
+ IpCollision, ///< Somebody is already using that fake-ip-address
+ WrongVersion, ///< The room version is not the same as for this RoomMember
+ WrongPassword, ///< The password doesn't match the one from the Room
+ CouldNotConnect, ///< The room is not responding to a connection attempt
+ RoomIsFull, ///< Room is already at the maximum number of players
+ HostBanned, ///< The user is banned by the host
+
+ // Reasons why moderation request failed
+ PermissionDenied, ///< The user does not have mod permissions
+ NoSuchUser, ///< The nickname the user attempts to kick/ban does not exist
+ };
+
+ struct MemberInformation {
+ std::string nickname; ///< Nickname of the member.
+ std::string username; ///< The web services username of the member. Can be empty.
+ std::string display_name; ///< The web services display name of the member. Can be empty.
+ std::string avatar_url; ///< Url to the member's avatar. Can be empty.
+ GameInfo game_info; ///< Name of the game they're currently playing, or empty if they're
+ /// not playing anything.
+ IPv4Address fake_ip; ///< Fake Ip address associated with this member.
+ };
+ using MemberList = std::vector<MemberInformation>;
+
+ // The handle for the callback functions
+ template <typename T>
+ using CallbackHandle = std::shared_ptr<std::function<void(const T&)>>;
+
+ /**
+ * Unbinds a callback function from the events.
+ * @param handle The connection handle to disconnect
+ */
+ template <typename T>
+ void Unbind(CallbackHandle<T> handle);
+
+ RoomMember();
+ ~RoomMember();
+
+ /**
+ * Returns the status of our connection to the room.
+ */
+ State GetState() const;
+
+ /**
+ * Returns information about the members in the room we're currently connected to.
+ */
+ const MemberList& GetMemberInformation() const;
+
+ /**
+ * Returns the nickname of the RoomMember.
+ */
+ const std::string& GetNickname() const;
+
+ /**
+ * Returns the username of the RoomMember.
+ */
+ const std::string& GetUsername() const;
+
+ /**
+ * Returns the MAC address of the RoomMember.
+ */
+ const IPv4Address& GetFakeIpAddress() const;
+
+ /**
+ * Returns information about the room we're currently connected to.
+ */
+ RoomInformation GetRoomInformation() const;
+
+ /**
+ * Returns whether we're connected to a server or not.
+ */
+ bool IsConnected() const;
+
+ /**
+ * Attempts to join a room at the specified address and port, using the specified nickname.
+ */
+ void Join(const std::string& nickname, const char* server_addr = "127.0.0.1",
+ u16 server_port = DefaultRoomPort, u16 client_port = 0,
+ const IPv4Address& preferred_fake_ip = NoPreferredIP,
+ const std::string& password = "", const std::string& token = "");
+
+ /**
+ * Sends a Proxy packet to the room.
+ * @param packet The WiFi packet to send.
+ */
+ void SendProxyPacket(const ProxyPacket& packet);
+
+ /**
+ * Sends an LDN packet to the room.
+ * @param packet The WiFi packet to send.
+ */
+ void SendLdnPacket(const LDNPacket& packet);
+
+ /**
+ * Sends a chat message to the room.
+ * @param message The contents of the message.
+ */
+ void SendChatMessage(const std::string& message);
+
+ /**
+ * Sends the current game info to the room.
+ * @param game_info The game information.
+ */
+ void SendGameInfo(const GameInfo& game_info);
+
+ /**
+ * Sends a moderation request to the room.
+ * @param type Moderation request type.
+ * @param nickname The subject of the request. (i.e. the user you want to kick/ban)
+ */
+ void SendModerationRequest(RoomMessageTypes type, const std::string& nickname);
+
+ /**
+ * Attempts to retrieve ban list from the room.
+ * If success, the ban list callback would be called. Otherwise an error would be emitted.
+ */
+ void RequestBanList();
+
+ /**
+ * Binds a function to an event that will be triggered every time the State of the member
+ * changed. The function wil be called every time the event is triggered. The callback function
+ * must not bind or unbind a function. Doing so will cause a deadlock
+ * @param callback The function to call
+ * @return A handle used for removing the function from the registered list
+ */
+ CallbackHandle<State> BindOnStateChanged(std::function<void(const State&)> callback);
+
+ /**
+ * Binds a function to an event that will be triggered every time an error happened. The
+ * function wil be called every time the event is triggered. The callback function must not bind
+ * or unbind a function. Doing so will cause a deadlock
+ * @param callback The function to call
+ * @return A handle used for removing the function from the registered list
+ */
+ CallbackHandle<Error> BindOnError(std::function<void(const Error&)> callback);
+
+ /**
+ * Binds a function to an event that will be triggered every time a ProxyPacket is received.
+ * The function wil be called everytime the event is triggered.
+ * The callback function must not bind or unbind a function. Doing so will cause a deadlock
+ * @param callback The function to call
+ * @return A handle used for removing the function from the registered list
+ */
+ CallbackHandle<ProxyPacket> BindOnProxyPacketReceived(
+ std::function<void(const ProxyPacket&)> callback);
+
+ /**
+ * Binds a function to an event that will be triggered every time an LDNPacket is received.
+ * The function wil be called everytime the event is triggered.
+ * The callback function must not bind or unbind a function. Doing so will cause a deadlock
+ * @param callback The function to call
+ * @return A handle used for removing the function from the registered list
+ */
+ CallbackHandle<LDNPacket> BindOnLdnPacketReceived(
+ std::function<void(const LDNPacket&)> callback);
+
+ /**
+ * Binds a function to an event that will be triggered every time the RoomInformation changes.
+ * The function wil be called every time the event is triggered.
+ * The callback function must not bind or unbind a function. Doing so will cause a deadlock
+ * @param callback The function to call
+ * @return A handle used for removing the function from the registered list
+ */
+ CallbackHandle<RoomInformation> BindOnRoomInformationChanged(
+ std::function<void(const RoomInformation&)> callback);
+
+ /**
+ * Binds a function to an event that will be triggered every time a ChatMessage is received.
+ * The function wil be called every time the event is triggered.
+ * The callback function must not bind or unbind a function. Doing so will cause a deadlock
+ * @param callback The function to call
+ * @return A handle used for removing the function from the registered list
+ */
+ CallbackHandle<ChatEntry> BindOnChatMessageRecieved(
+ std::function<void(const ChatEntry&)> callback);
+
+ /**
+ * Binds a function to an event that will be triggered every time a StatusMessage is
+ * received. The function will be called every time the event is triggered. The callback
+ * function must not bind or unbind a function. Doing so will cause a deadlock
+ * @param callback The function to call
+ * @return A handle used for removing the function from the registered list
+ */
+ CallbackHandle<StatusMessageEntry> BindOnStatusMessageReceived(
+ std::function<void(const StatusMessageEntry&)> callback);
+
+ /**
+ * Binds a function to an event that will be triggered every time a requested ban list
+ * received. The function will be called every time the event is triggered. The callback
+ * function must not bind or unbind a function. Doing so will cause a deadlock
+ * @param callback The function to call
+ * @return A handle used for removing the function from the registered list
+ */
+ CallbackHandle<Room::BanList> BindOnBanListReceived(
+ std::function<void(const Room::BanList&)> callback);
+
+ /**
+ * Leaves the current room.
+ */
+ void Leave();
+
+private:
+ class RoomMemberImpl;
+ std::unique_ptr<RoomMemberImpl> room_member_impl;
+};
+
+inline const char* GetStateStr(const RoomMember::State& s) {
+ switch (s) {
+ case RoomMember::State::Uninitialized:
+ return "Uninitialized";
+ case RoomMember::State::Idle:
+ return "Idle";
+ case RoomMember::State::Joining:
+ return "Joining";
+ case RoomMember::State::Joined:
+ return "Joined";
+ case RoomMember::State::Moderator:
+ return "Moderator";
+ }
+ return "Unknown";
+}
+
+inline const char* GetErrorStr(const RoomMember::Error& e) {
+ switch (e) {
+ case RoomMember::Error::LostConnection:
+ return "LostConnection";
+ case RoomMember::Error::HostKicked:
+ return "HostKicked";
+ case RoomMember::Error::UnknownError:
+ return "UnknownError";
+ case RoomMember::Error::NameCollision:
+ return "NameCollision";
+ case RoomMember::Error::IpCollision:
+ return "IpCollision";
+ case RoomMember::Error::WrongVersion:
+ return "WrongVersion";
+ case RoomMember::Error::WrongPassword:
+ return "WrongPassword";
+ case RoomMember::Error::CouldNotConnect:
+ return "CouldNotConnect";
+ case RoomMember::Error::RoomIsFull:
+ return "RoomIsFull";
+ case RoomMember::Error::HostBanned:
+ return "HostBanned";
+ case RoomMember::Error::PermissionDenied:
+ return "PermissionDenied";
+ case RoomMember::Error::NoSuchUser:
+ return "NoSuchUser";
+ default:
+ return "Unknown";
+ }
+}
+
+} // namespace Network
diff --git a/src/network/verify_user.cpp b/src/network/verify_user.cpp
new file mode 100644
index 000000000..f84cfe59b
--- /dev/null
+++ b/src/network/verify_user.cpp
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "network/verify_user.h"
+
+namespace Network::VerifyUser {
+
+Backend::~Backend() = default;
+
+NullBackend::~NullBackend() = default;
+
+UserData NullBackend::LoadUserData([[maybe_unused]] const std::string& verify_uid,
+ [[maybe_unused]] const std::string& token) {
+ return {};
+}
+
+} // namespace Network::VerifyUser
diff --git a/src/network/verify_user.h b/src/network/verify_user.h
new file mode 100644
index 000000000..6fc64d8a3
--- /dev/null
+++ b/src/network/verify_user.h
@@ -0,0 +1,45 @@
+// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+#include "common/logging/log.h"
+
+namespace Network::VerifyUser {
+
+struct UserData {
+ std::string username;
+ std::string display_name;
+ std::string avatar_url;
+ bool moderator = false; ///< Whether the user is a yuzu Moderator.
+};
+
+/**
+ * A backend used for verifying users and loading user data.
+ */
+class Backend {
+public:
+ virtual ~Backend();
+
+ /**
+ * Verifies the given token and loads the information into a UserData struct.
+ * @param verify_uid A GUID that may be used for verification.
+ * @param token A token that contains user data and verification data. The format and content is
+ * decided by backends.
+ */
+ virtual UserData LoadUserData(const std::string& verify_uid, const std::string& token) = 0;
+};
+
+/**
+ * A null backend where the token is ignored.
+ * No verification is performed here and the function returns an empty UserData.
+ */
+class NullBackend final : public Backend {
+public:
+ ~NullBackend();
+
+ UserData LoadUserData(const std::string& verify_uid, const std::string& token) override;
+};
+
+} // namespace Network::VerifyUser
diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt
index 4c76ce1ea..af8e51fe8 100644
--- a/src/shader_recompiler/CMakeLists.txt
+++ b/src/shader_recompiler/CMakeLists.txt
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
add_library(shader_recompiler STATIC
backend/bindings.h
backend/glasm/emit_glasm.cpp
@@ -253,9 +256,6 @@ else()
-Werror
-Werror=conversion
-Werror=ignored-qualifiers
- -Werror=implicit-fallthrough
- -Werror=shadow
- -Werror=sign-compare
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
-Werror=unused-variable
diff --git a/src/shader_recompiler/backend/bindings.h b/src/shader_recompiler/backend/bindings.h
index 669702553..b2598fd52 100644
--- a/src/shader_recompiler/backend/bindings.h
+++ b/src/shader_recompiler/backend/bindings.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm.cpp b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
index 42eff443f..01f9abc71 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <string>
@@ -176,7 +175,7 @@ bool IsReference(IR::Inst& inst) {
}
void PrecolorInst(IR::Inst& phi) {
- // Insert phi moves before references to avoid overwritting other phis
+ // Insert phi moves before references to avoid overwriting other phis
const size_t num_args{phi.NumArgs()};
for (size_t i = 0; i < num_args; ++i) {
IR::Block& phi_block{*phi.PhiBlock(i)};
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm.h b/src/shader_recompiler/backend/glasm/emit_glasm.h
index 292655acb..01a2efeff 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm.h
+++ b/src/shader_recompiler/backend/glasm/emit_glasm.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_barriers.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_barriers.cpp
index c0b97683e..c42fa6cd1 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_barriers.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_barriers.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp
index 3bfcbbe65..2fc2a0ac6 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp
index babbe6654..d508ee567 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
index 7434a1f92..7e8f37563 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
@@ -14,9 +13,6 @@ namespace Shader::Backend::GLASM {
namespace {
void GetCbuf(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding, ScalarU32 offset,
std::string_view size) {
- if (!binding.IsImmediate()) {
- throw NotImplementedException("Indirect constant buffer loading");
- }
const Register ret{ctx.reg_alloc.Define(inst)};
if (offset.type == Type::U32) {
// Avoid reading arrays out of bounds, matching hardware's behavior
@@ -25,7 +21,27 @@ void GetCbuf(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding, ScalarU
return;
}
}
- ctx.Add("LDC.{} {},c{}[{}];", size, ret, binding.U32(), offset);
+
+ if (binding.IsImmediate()) {
+ ctx.Add("LDC.{} {},c{}[{}];", size, ret, binding.U32(), offset);
+ return;
+ }
+
+ const ScalarU32 idx{ctx.reg_alloc.Consume(binding)};
+ for (u32 i = 0; i < Info::MAX_INDIRECT_CBUFS; i++) {
+ ctx.Add("SEQ.S.CC RC.x,{},{};"
+ "IF NE.x;"
+ "LDC.{} {},c{}[{}];",
+ idx, i, size, ret, i, offset);
+
+ if (i != Info::MAX_INDIRECT_CBUFS - 1) {
+ ctx.Add("ELSE;");
+ }
+ }
+
+ for (u32 i = 0; i < Info::MAX_INDIRECT_CBUFS; i++) {
+ ctx.Add("ENDIF;");
+ }
}
bool IsInputArray(Stage stage) {
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_control_flow.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_control_flow.cpp
index 8a14fc8d9..078c75b20 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_control_flow.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_control_flow.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_convert.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_convert.cpp
index 4cff70fe4..473dc8364 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_convert.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_convert.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string_view>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_floating_point.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_floating_point.cpp
index 356640471..e38ae49b0 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_floating_point.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_floating_point.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string_view>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp
index 237a5af3f..e67e80fac 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <utility>
@@ -68,6 +67,7 @@ std::string_view TextureType(IR::TextureInstInfo info) {
case TextureType::ColorArray1D:
return "SHADOWARRAY1D";
case TextureType::Color2D:
+ case TextureType::Color2DRect:
return "SHADOW2D";
case TextureType::ColorArray2D:
return "SHADOWARRAY2D";
@@ -87,6 +87,7 @@ std::string_view TextureType(IR::TextureInstInfo info) {
case TextureType::ColorArray1D:
return "ARRAY1D";
case TextureType::Color2D:
+ case TextureType::Color2DRect:
return "2D";
case TextureType::ColorArray2D:
return "ARRAY2D";
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
index 5efbe4e6f..8b0ac3031 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_integer.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_integer.cpp
index f698b8b9b..528240f44 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_integer.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_integer.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_logical.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_logical.cpp
index eed7bfec2..b0968c0ee 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_logical.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_logical.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
index f0fd94a28..2705ab140 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string_view>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_not_implemented.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_not_implemented.cpp
index 86287ee3f..7094d8e42 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_not_implemented.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_not_implemented.cpp
@@ -1,12 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string_view>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
-#include "shader_recompiler/frontend/ir/program.h"
#include "shader_recompiler/frontend/ir/value.h"
#ifdef _MSC_VER
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_select.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_select.cpp
index dc441c56d..fcb3fe3cb 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_select.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_select.cpp
@@ -1,7 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_shared_memory.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_shared_memory.cpp
index 39e1c6c3a..5c1a488a0 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_shared_memory.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_shared_memory.cpp
@@ -1,7 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_special.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_special.cpp
index e7a5fb13a..368975492 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_special.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_special.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_undefined.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_undefined.cpp
index 875e9d991..69fec3ff7 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_undefined.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_undefined.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_warp.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_warp.cpp
index 32e0dd923..fb6a597d4 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_warp.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_warp.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
diff --git a/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp b/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp
index 0401953f7..89603c1c4 100644
--- a/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp
+++ b/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string_view>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/bindings.h"
#include "shader_recompiler/backend/glasm/emit_glasm.h"
diff --git a/src/shader_recompiler/backend/glasm/glasm_emit_context.h b/src/shader_recompiler/backend/glasm/glasm_emit_context.h
index 8433e5c00..a81d64a9e 100644
--- a/src/shader_recompiler/backend/glasm/glasm_emit_context.h
+++ b/src/shader_recompiler/backend/glasm/glasm_emit_context.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/backend/glasm/reg_alloc.cpp b/src/shader_recompiler/backend/glasm/reg_alloc.cpp
index 201e428c1..781803e55 100644
--- a/src/shader_recompiler/backend/glasm/reg_alloc.cpp
+++ b/src/shader_recompiler/backend/glasm/reg_alloc.cpp
@@ -1,12 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/format.h>
-#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
#include "shader_recompiler/backend/glasm/reg_alloc.h"
#include "shader_recompiler/exception.h"
#include "shader_recompiler/frontend/ir/value.h"
diff --git a/src/shader_recompiler/backend/glasm/reg_alloc.h b/src/shader_recompiler/backend/glasm/reg_alloc.h
index 82aec66c6..bd6e2d929 100644
--- a/src/shader_recompiler/backend/glasm/reg_alloc.h
+++ b/src/shader_recompiler/backend/glasm/reg_alloc.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl.cpp b/src/shader_recompiler/backend/glsl/emit_glsl.cpp
index b6b17a330..e8a4390f6 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <string>
@@ -102,7 +101,7 @@ bool IsReference(IR::Inst& inst) {
}
void PrecolorInst(IR::Inst& phi) {
- // Insert phi moves before references to avoid overwritting other phis
+ // Insert phi moves before references to avoid overwriting other phis
const size_t num_args{phi.NumArgs()};
for (size_t i = 0; i < num_args; ++i) {
IR::Block& phi_block{*phi.PhiBlock(i)};
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl.h b/src/shader_recompiler/backend/glsl/emit_glsl.h
index 20e5719e6..1c6d1472b 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl.h
+++ b/src/shader_recompiler/backend/glsl/emit_glsl.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_atomic.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_atomic.cpp
index a409a7ab3..911181c43 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_atomic.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_atomic.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_barriers.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_barriers.cpp
index 8a9faa394..2cf614045 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_barriers.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_barriers.cpp
@@ -1,10 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glsl/emit_glsl_instructions.h"
#include "shader_recompiler/backend/glsl/glsl_emit_context.h"
-#include "shader_recompiler/frontend/ir/value.h"
namespace Shader::Backend::GLSL {
void EmitBarrier(EmitContext& ctx) {
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp
index e0ead7a53..1be4a0f59 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_composite.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_composite.cpp
index 98cc57e58..14bc824be 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_composite.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_composite.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
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..fad8d1e30 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
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
@@ -35,6 +34,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 +63,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 +148,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 +170,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/emit_glsl_control_flow.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_control_flow.cpp
index c86465e8b..91a087487 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_control_flow.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_control_flow.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string_view>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glsl/emit_glsl_instructions.h"
#include "shader_recompiler/backend/glsl/glsl_emit_context.h"
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_convert.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_convert.cpp
index ce6ea1bb7..aeacb5a64 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_convert.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_convert.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_floating_point.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_floating_point.cpp
index 474189d87..882a48c43 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_floating_point.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_floating_point.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
index fae2e397a..cecdbb9d6 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
@@ -467,6 +466,7 @@ void EmitImageQueryDimensions(EmitContext& ctx, IR::Inst& inst, const IR::Value&
case TextureType::ColorArray1D:
case TextureType::Color2D:
case TextureType::ColorCube:
+ case TextureType::Color2DRect:
return ctx.AddU32x4(
"{}=uvec4(uvec2(textureSize({},int({}))),0u,uint(textureQueryLevels({})));", inst,
texture, lod, texture);
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
index 704baddc9..639691ba6 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_integer.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_integer.cpp
index b0d85be99..49397c9b2 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_integer.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_integer.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string_view>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glsl/emit_glsl_instructions.h"
#include "shader_recompiler/backend/glsl/glsl_emit_context.h"
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_logical.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_logical.cpp
index 742fec9cf..97d5bd9bd 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_logical.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_logical.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string_view>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glsl/emit_glsl_instructions.h"
#include "shader_recompiler/backend/glsl/glsl_emit_context.h"
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_memory.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_memory.cpp
index 9fd41b4fd..28bd3f5b3 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_memory.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_memory.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_not_implemented.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_not_implemented.cpp
index 4ebdfb3bc..b03a8ba1e 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_not_implemented.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_not_implemented.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_select.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_select.cpp
index b1e486e5f..d570f2328 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_select.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_select.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_shared_memory.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_shared_memory.cpp
index 74ae345e5..5095f5a29 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_shared_memory.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_shared_memory.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string_view>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glsl/emit_glsl_instructions.h"
#include "shader_recompiler/backend/glsl/glsl_emit_context.h"
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_special.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_special.cpp
index fcf620b79..67a54a84f 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_special.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_special.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string_view>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glsl/emit_glsl_instructions.h"
#include "shader_recompiler/backend/glsl/glsl_emit_context.h"
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_undefined.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_undefined.cpp
index cace1db85..5f6edcf02 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_undefined.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_undefined.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string_view>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/glsl/emit_glsl_instructions.h"
#include "shader_recompiler/backend/glsl/glsl_emit_context.h"
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp
index 6e01979b4..1245c9429 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
diff --git a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
index e816a93ec..c767a9dc3 100644
--- a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
+++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/bindings.h"
#include "shader_recompiler/backend/glsl/glsl_emit_context.h"
@@ -87,6 +86,7 @@ std::string_view SamplerType(TextureType type, bool is_depth) {
case TextureType::ColorArray1D:
return "sampler1DArray";
case TextureType::Color2D:
+ case TextureType::Color2DRect:
return "sampler2D";
case TextureType::ColorArray2D:
return "sampler2DArray";
@@ -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..dfd10ac28 100644
--- a/src/shader_recompiler/backend/glsl/glsl_emit_context.h
+++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -162,6 +161,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/glsl/var_alloc.cpp b/src/shader_recompiler/backend/glsl/var_alloc.cpp
index be0a695c0..8b7da539a 100644
--- a/src/shader_recompiler/backend/glsl/var_alloc.cpp
+++ b/src/shader_recompiler/backend/glsl/var_alloc.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include <string_view>
diff --git a/src/shader_recompiler/backend/glsl/var_alloc.h b/src/shader_recompiler/backend/glsl/var_alloc.h
index 8b49f32a6..c571473ed 100644
--- a/src/shader_recompiler/backend/glsl/var_alloc.h
+++ b/src/shader_recompiler/backend/glsl/var_alloc.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp
index 08b3a81ce..265ac9c85 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <span>
#include <tuple>
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.h b/src/shader_recompiler/backend/spirv/emit_spirv.h
index b412957c7..7567b6fc9 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv.h
+++ b/src/shader_recompiler/backend/spirv/emit_spirv.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -22,7 +21,7 @@ constexpr u32 NUM_TEXTURE_AND_IMAGE_SCALING_WORDS =
struct RescalingLayout {
alignas(16) std::array<u32, NUM_TEXTURE_SCALING_WORDS> rescaling_textures;
alignas(16) std::array<u32, NUM_IMAGE_SCALING_WORDS> rescaling_images;
- alignas(16) u32 down_factor;
+ u32 down_factor;
};
constexpr u32 RESCALING_LAYOUT_WORDS_OFFSET = offsetof(RescalingLayout, rescaling_textures);
constexpr u32 RESCALING_LAYOUT_DOWN_FACTOR_OFFSET = offsetof(RescalingLayout, down_factor);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
index d3cbb14a9..4b3043b65 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
@@ -1,6 +1,7 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <bit>
#include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_barriers.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_barriers.cpp
index 9ce95a41b..0b9a3664f 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_barriers.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_barriers.cpp
@@ -1,11 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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"
-#include "shader_recompiler/frontend/ir/modifiers.h"
namespace Shader::Backend::SPIRV {
namespace {
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp
index 02d1e63f7..c4ca28d11 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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_composite.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_composite.cpp
index 5c3e1ee2b..e757e9aed 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_composite.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_composite.cpp
@@ -1,11 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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"
-#include "shader_recompiler/frontend/ir/modifiers.h"
namespace Shader::Backend::SPIRV {
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 8ea730c80..2c68aba39 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
@@ -1,11 +1,10 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <bit>
#include <tuple>
#include <utility>
-#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"
@@ -123,34 +122,36 @@ std::optional<OutAttr> OutputAttrPointer(EmitContext& ctx, IR::Attribute attr) {
}
Id GetCbuf(EmitContext& ctx, Id result_type, Id UniformDefinitions::*member_ptr, u32 element_size,
- const IR::Value& binding, const IR::Value& offset) {
+ const IR::Value& binding, const IR::Value& offset, const Id indirect_func) {
+ Id buffer_offset;
+ const Id uniform_type{ctx.uniform_types.*member_ptr};
+ if (offset.IsImmediate()) {
+ // Hardware been proved to read the aligned offset (e.g. LDC.U32 at 6 will read offset 4)
+ const Id imm_offset{ctx.Const(offset.U32() / element_size)};
+ buffer_offset = imm_offset;
+ } else if (element_size > 1) {
+ const u32 log2_element_size{static_cast<u32>(std::countr_zero(element_size))};
+ const Id shift{ctx.Const(log2_element_size)};
+ buffer_offset = ctx.OpShiftRightArithmetic(ctx.U32[1], ctx.Def(offset), shift);
+ } else {
+ buffer_offset = ctx.Def(offset);
+ }
if (!binding.IsImmediate()) {
- throw NotImplementedException("Constant buffer indexing");
+ return ctx.OpFunctionCall(result_type, indirect_func, ctx.Def(binding), buffer_offset);
}
const Id cbuf{ctx.cbufs[binding.U32()].*member_ptr};
- const Id uniform_type{ctx.uniform_types.*member_ptr};
- if (!offset.IsImmediate()) {
- Id index{ctx.Def(offset)};
- if (element_size > 1) {
- const u32 log2_element_size{static_cast<u32>(std::countr_zero(element_size))};
- const Id shift{ctx.Const(log2_element_size)};
- index = ctx.OpShiftRightArithmetic(ctx.U32[1], ctx.Def(offset), shift);
- }
- const Id access_chain{ctx.OpAccessChain(uniform_type, cbuf, ctx.u32_zero_value, index)};
- return ctx.OpLoad(result_type, access_chain);
- }
- // Hardware been proved to read the aligned offset (e.g. LDC.U32 at 6 will read offset 4)
- const Id imm_offset{ctx.Const(offset.U32() / element_size)};
- const Id access_chain{ctx.OpAccessChain(uniform_type, cbuf, ctx.u32_zero_value, imm_offset)};
+ const Id access_chain{ctx.OpAccessChain(uniform_type, cbuf, ctx.u32_zero_value, buffer_offset)};
return ctx.OpLoad(result_type, access_chain);
}
Id GetCbufU32(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
- return GetCbuf(ctx, ctx.U32[1], &UniformDefinitions::U32, sizeof(u32), binding, offset);
+ return GetCbuf(ctx, ctx.U32[1], &UniformDefinitions::U32, sizeof(u32), binding, offset,
+ ctx.load_const_func_u32);
}
Id GetCbufU32x4(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
- return GetCbuf(ctx, ctx.U32[4], &UniformDefinitions::U32x4, sizeof(u32[4]), binding, offset);
+ return GetCbuf(ctx, ctx.U32[4], &UniformDefinitions::U32x4, sizeof(u32[4]), binding, offset,
+ ctx.load_const_func_u32x4);
}
Id GetCbufElement(EmitContext& ctx, Id vector, const IR::Value& offset, u32 index_offset) {
@@ -201,7 +202,8 @@ void EmitGetIndirectBranchVariable(EmitContext&) {
Id EmitGetCbufU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8) {
- const Id load{GetCbuf(ctx, ctx.U8, &UniformDefinitions::U8, sizeof(u8), binding, offset)};
+ const Id load{GetCbuf(ctx, ctx.U8, &UniformDefinitions::U8, sizeof(u8), binding, offset,
+ ctx.load_const_func_u8)};
return ctx.OpUConvert(ctx.U32[1], load);
}
Id element{};
@@ -217,7 +219,8 @@ Id EmitGetCbufU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& of
Id EmitGetCbufS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8) {
- const Id load{GetCbuf(ctx, ctx.S8, &UniformDefinitions::S8, sizeof(s8), binding, offset)};
+ const Id load{GetCbuf(ctx, ctx.S8, &UniformDefinitions::S8, sizeof(s8), binding, offset,
+ ctx.load_const_func_u8)};
return ctx.OpSConvert(ctx.U32[1], load);
}
Id element{};
@@ -233,8 +236,8 @@ Id EmitGetCbufS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& of
Id EmitGetCbufU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16) {
- const Id load{
- GetCbuf(ctx, ctx.U16, &UniformDefinitions::U16, sizeof(u16), binding, offset)};
+ const Id load{GetCbuf(ctx, ctx.U16, &UniformDefinitions::U16, sizeof(u16), binding, offset,
+ ctx.load_const_func_u16)};
return ctx.OpUConvert(ctx.U32[1], load);
}
Id element{};
@@ -250,8 +253,8 @@ Id EmitGetCbufU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& o
Id EmitGetCbufS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16) {
- const Id load{
- GetCbuf(ctx, ctx.S16, &UniformDefinitions::S16, sizeof(s16), binding, offset)};
+ const Id load{GetCbuf(ctx, ctx.S16, &UniformDefinitions::S16, sizeof(s16), binding, offset,
+ ctx.load_const_func_u16)};
return ctx.OpSConvert(ctx.U32[1], load);
}
Id element{};
@@ -276,7 +279,8 @@ Id EmitGetCbufU32(EmitContext& ctx, const IR::Value& binding, const IR::Value& o
Id EmitGetCbufF32(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_descriptor_aliasing) {
- return GetCbuf(ctx, ctx.F32[1], &UniformDefinitions::F32, sizeof(f32), binding, offset);
+ return GetCbuf(ctx, ctx.F32[1], &UniformDefinitions::F32, sizeof(f32), binding, offset,
+ ctx.load_const_func_f32);
} else {
const Id vector{GetCbufU32x4(ctx, binding, offset)};
return ctx.OpBitcast(ctx.F32[1], GetCbufElement(ctx, vector, offset, 0u));
@@ -285,8 +289,8 @@ Id EmitGetCbufF32(EmitContext& ctx, const IR::Value& binding, const IR::Value& o
Id EmitGetCbufU32x2(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_descriptor_aliasing) {
- return GetCbuf(ctx, ctx.U32[2], &UniformDefinitions::U32x2, sizeof(u32[2]), binding,
- offset);
+ return GetCbuf(ctx, ctx.U32[2], &UniformDefinitions::U32x2, sizeof(u32[2]), binding, offset,
+ ctx.load_const_func_u32x2);
} else {
const Id vector{GetCbufU32x4(ctx, binding, offset)};
return ctx.OpCompositeConstruct(ctx.U32[2], GetCbufElement(ctx, vector, offset, 0u),
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_control_flow.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_control_flow.cpp
index 1eca3aa85..7ad0b08ac 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_control_flow.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_control_flow.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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_convert.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp
index 832de2452..a88f1b7c4 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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_floating_point.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp
index 0cdc46495..c740c96fb 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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"
#include "shader_recompiler/frontend/ir/modifiers.h"
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
index d18d5f1d5..fb5799c42 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <boost/container/static_vector.hpp>
@@ -454,6 +453,7 @@ Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& i
case TextureType::ColorArray1D:
case TextureType::Color2D:
case TextureType::ColorCube:
+ case TextureType::Color2DRect:
return ctx.OpCompositeConstruct(ctx.U32[4], ctx.OpImageQuerySizeLod(ctx.U32[2], image, lod),
zero, mips());
case TextureType::ColorArray2D:
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp
index a96190bc6..2a12feddc 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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"
#include "shader_recompiler/frontend/ir/modifiers.h"
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
index f263b41b0..984d072b4 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp
index 44521f539..960bdea6f 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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_logical.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_logical.cpp
index 47745f7ee..c38427c93 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_logical.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_logical.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp
index 175f4be19..8693801c7 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <bit>
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_select.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_select.cpp
index 48caf1ffc..68e5b1c8e 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_select.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_select.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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_shared_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp
index 330c9052c..df05dad74 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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_special.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp
index d96a17583..00be1f127 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_undefined.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_undefined.cpp
index b5766fc52..0649290b0 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_undefined.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_undefined.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp
index 7034228bf..7cbbbfaa6 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#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/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index cd90c084a..aecc4c612 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -1,11 +1,10 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
+#include <bit>
#include <climits>
-#include <string_view>
#include <boost/container/static_vector.hpp>
@@ -42,6 +41,7 @@ Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) {
case TextureType::ColorArray1D:
return ctx.TypeImage(type, spv::Dim::Dim1D, depth, true, false, 1, format);
case TextureType::Color2D:
+ case TextureType::Color2DRect:
return ctx.TypeImage(type, spv::Dim::Dim2D, depth, false, false, 1, format);
case TextureType::ColorArray2D:
return ctx.TypeImage(type, spv::Dim::Dim2D, depth, true, false, 1, format);
@@ -464,6 +464,7 @@ EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_inf
DefineSharedMemory(program);
DefineSharedMemoryFunctions(program);
DefineConstantBuffers(program.info, uniform_binding);
+ DefineConstantBufferIndirectFunctions(program.info);
DefineStorageBuffers(program.info, storage_binding);
DefineTextureBuffers(program.info, texture_binding);
DefineImageBuffers(program.info, image_binding);
@@ -993,7 +994,7 @@ void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) {
}
return;
}
- IR::Type types{info.used_constant_buffer_types};
+ IR::Type types{info.used_constant_buffer_types | info.used_indirect_cbuf_types};
if (True(types & IR::Type::U8)) {
if (profile.support_int8) {
DefineConstBuffers(*this, info, &UniformDefinitions::U8, binding, U8, 'u', sizeof(u8));
@@ -1027,6 +1028,63 @@ void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) {
binding += static_cast<u32>(info.constant_buffer_descriptors.size());
}
+void EmitContext::DefineConstantBufferIndirectFunctions(const Info& info) {
+ if (!info.uses_cbuf_indirect) {
+ return;
+ }
+ const auto make_accessor{[&](Id buffer_type, Id UniformDefinitions::*member_ptr) {
+ const Id func_type{TypeFunction(buffer_type, U32[1], U32[1])};
+ const Id func{OpFunction(buffer_type, spv::FunctionControlMask::MaskNone, func_type)};
+ const Id binding{OpFunctionParameter(U32[1])};
+ const Id offset{OpFunctionParameter(U32[1])};
+
+ AddLabel();
+
+ const Id merge_label{OpLabel()};
+ const Id uniform_type{uniform_types.*member_ptr};
+
+ 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_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)};
+ const Id result{OpLoad(buffer_type, access_chain)};
+ OpReturnValue(result);
+ }
+ AddLabel(merge_label);
+ OpUnreachable();
+ OpFunctionEnd();
+ return func;
+ }};
+ IR::Type types{info.used_indirect_cbuf_types};
+ 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 (supports_aliasing && True(types & IR::Type::U16)) {
+ load_const_func_u16 = make_accessor(U16, &UniformDefinitions::U16);
+ }
+ if (supports_aliasing && True(types & IR::Type::F32)) {
+ load_const_func_f32 = make_accessor(F32[1], &UniformDefinitions::F32);
+ }
+ if (supports_aliasing && True(types & IR::Type::U32)) {
+ load_const_func_u32 = make_accessor(U32[1], &UniformDefinitions::U32);
+ }
+ if (supports_aliasing && True(types & IR::Type::U32x2)) {
+ load_const_func_u32x2 = make_accessor(U32[2], &UniformDefinitions::U32x2);
+ }
+ if (!supports_aliasing || True(types & IR::Type::U32x4)) {
+ load_const_func_u32x4 = make_accessor(U32[4], &UniformDefinitions::U32x4);
+ }
+}
+
void EmitContext::DefineStorageBuffers(const Info& info, u32& binding) {
if (info.storage_buffers_descriptors.empty()) {
return;
@@ -1249,7 +1307,7 @@ void EmitContext::DefineInputs(const IR::Program& program) {
subgroup_mask_gt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGtMaskKHR);
subgroup_mask_ge = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGeMaskKHR);
}
- if (info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
+ if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
(profile.warp_size_potentially_larger_than_guest &&
(info.uses_subgroup_vote || info.uses_subgroup_mask))) {
subgroup_local_invocation_id =
@@ -1354,7 +1412,8 @@ void EmitContext::DefineInputs(const IR::Program& program) {
void EmitContext::DefineOutputs(const IR::Program& program) {
const Info& info{program.info};
const std::optional<u32> invocations{program.invocations};
- if (info.stores.AnyComponent(IR::Attribute::PositionX) || stage == Stage::VertexB) {
+ if (runtime_info.convert_depth_mode || info.stores.AnyComponent(IR::Attribute::PositionX) ||
+ stage == Stage::VertexB) {
output_position = DefineOutput(*this, F32[4], invocations, spv::BuiltIn::Position);
}
if (info.stores[IR::Attribute::PointSize] || runtime_info.fixed_state_point_size) {
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
index f87138f7e..bc25b8b84 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
@@ -1,11 +1,9 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
-#include <string_view>
#include <sirit/sirit.h>
@@ -294,6 +292,13 @@ public:
std::vector<Id> interfaces;
+ Id load_const_func_u8{};
+ Id load_const_func_u16{};
+ Id load_const_func_u32{};
+ Id load_const_func_f32{};
+ Id load_const_func_u32x2{};
+ Id load_const_func_u32x4{};
+
private:
void DefineCommonTypes(const Info& info);
void DefineCommonConstants();
@@ -302,6 +307,7 @@ private:
void DefineSharedMemory(const IR::Program& program);
void DefineSharedMemoryFunctions(const IR::Program& program);
void DefineConstantBuffers(const Info& info, u32& binding);
+ void DefineConstantBufferIndirectFunctions(const Info& info);
void DefineStorageBuffers(const Info& info, u32& binding);
void DefineTextureBuffers(const Info& info, u32& binding);
void DefineImageBuffers(const Info& info, u32& binding);
diff --git a/src/shader_recompiler/environment.h b/src/shader_recompiler/environment.h
index db16429d4..9729d48c6 100644
--- a/src/shader_recompiler/environment.h
+++ b/src/shader_recompiler/environment.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/exception.h b/src/shader_recompiler/exception.h
index d98b6029b..322fce657 100644
--- a/src/shader_recompiler/exception.h
+++ b/src/shader_recompiler/exception.h
@@ -1,12 +1,10 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <exception>
#include <string>
-#include <string_view>
#include <utility>
#include "common/logging/formatter.h"
diff --git a/src/shader_recompiler/frontend/ir/abstract_syntax_list.h b/src/shader_recompiler/frontend/ir/abstract_syntax_list.h
index b61773487..7edf21e8a 100644
--- a/src/shader_recompiler/frontend/ir/abstract_syntax_list.h
+++ b/src/shader_recompiler/frontend/ir/abstract_syntax_list.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/ir/attribute.cpp b/src/shader_recompiler/frontend/ir/attribute.cpp
index 4d0b8b8e5..7d3d882e4 100644
--- a/src/shader_recompiler/frontend/ir/attribute.cpp
+++ b/src/shader_recompiler/frontend/ir/attribute.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/format.h>
diff --git a/src/shader_recompiler/frontend/ir/attribute.h b/src/shader_recompiler/frontend/ir/attribute.h
index 3bbd38a03..6ee3947b1 100644
--- a/src/shader_recompiler/frontend/ir/attribute.h
+++ b/src/shader_recompiler/frontend/ir/attribute.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/ir/basic_block.cpp b/src/shader_recompiler/frontend/ir/basic_block.cpp
index 974efa4a0..14293770e 100644
--- a/src/shader_recompiler/frontend/ir/basic_block.cpp
+++ b/src/shader_recompiler/frontend/ir/basic_block.cpp
@@ -1,13 +1,10 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <initializer_list>
#include <map>
-#include <memory>
-#include "common/bit_cast.h"
#include "common/common_types.h"
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/value.h"
diff --git a/src/shader_recompiler/frontend/ir/basic_block.h b/src/shader_recompiler/frontend/ir/basic_block.h
index fbfe98266..c9d83661a 100644
--- a/src/shader_recompiler/frontend/ir/basic_block.h
+++ b/src/shader_recompiler/frontend/ir/basic_block.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/ir/breadth_first_search.h b/src/shader_recompiler/frontend/ir/breadth_first_search.h
index a52ccbd58..ec5542554 100644
--- a/src/shader_recompiler/frontend/ir/breadth_first_search.h
+++ b/src/shader_recompiler/frontend/ir/breadth_first_search.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/ir/condition.cpp b/src/shader_recompiler/frontend/ir/condition.cpp
index fc18ea2a2..fb6b141d3 100644
--- a/src/shader_recompiler/frontend/ir/condition.cpp
+++ b/src/shader_recompiler/frontend/ir/condition.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
diff --git a/src/shader_recompiler/frontend/ir/condition.h b/src/shader_recompiler/frontend/ir/condition.h
index aa8597c60..1cad46b9b 100644
--- a/src/shader_recompiler/frontend/ir/condition.h
+++ b/src/shader_recompiler/frontend/ir/condition.h
@@ -1,10 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <compare>
#include <string>
#include <fmt/format.h>
diff --git a/src/shader_recompiler/frontend/ir/flow_test.cpp b/src/shader_recompiler/frontend/ir/flow_test.cpp
index 6ebb4ad89..a859efdde 100644
--- a/src/shader_recompiler/frontend/ir/flow_test.cpp
+++ b/src/shader_recompiler/frontend/ir/flow_test.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
diff --git a/src/shader_recompiler/frontend/ir/flow_test.h b/src/shader_recompiler/frontend/ir/flow_test.h
index 09e113773..88f7c9e82 100644
--- a/src/shader_recompiler/frontend/ir/flow_test.h
+++ b/src/shader_recompiler/frontend/ir/flow_test.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.cpp b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
index 356f889ac..11086ed8c 100644
--- a/src/shader_recompiler/frontend/ir/ir_emitter.cpp
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_cast.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
@@ -1833,6 +1832,11 @@ Value IREmitter::ImageQueryDimension(const Value& handle, const IR::U32& lod) {
return Inst(op, handle, lod);
}
+Value IREmitter::ImageQueryDimension(const Value& handle, const IR::U32& lod,
+ TextureInstInfo info) {
+ return Inst(Opcode::ImageQueryDimensions, Flags{info}, handle, lod);
+}
+
Value IREmitter::ImageQueryLod(const Value& handle, const Value& coords, TextureInstInfo info) {
const Opcode op{handle.IsImmediate() ? Opcode::BoundImageQueryLod
: Opcode::BindlessImageQueryLod};
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.h b/src/shader_recompiler/frontend/ir/ir_emitter.h
index 13eefa88b..25839a371 100644
--- a/src/shader_recompiler/frontend/ir/ir_emitter.h
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -316,6 +315,8 @@ public:
const F32& dref, const F32& lod,
const Value& offset, TextureInstInfo info);
[[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod);
+ [[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod,
+ TextureInstInfo info);
[[nodiscard]] Value ImageQueryLod(const Value& handle, const Value& coords,
TextureInstInfo info);
diff --git a/src/shader_recompiler/frontend/ir/microinstruction.cpp b/src/shader_recompiler/frontend/ir/microinstruction.cpp
index 631446cf7..468782eb1 100644
--- a/src/shader_recompiler/frontend/ir/microinstruction.cpp
+++ b/src/shader_recompiler/frontend/ir/microinstruction.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <memory>
@@ -326,6 +325,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/modifiers.h b/src/shader_recompiler/frontend/ir/modifiers.h
index 77cda1f8a..69035d462 100644
--- a/src/shader_recompiler/frontend/ir/modifiers.h
+++ b/src/shader_recompiler/frontend/ir/modifiers.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/ir/opcodes.cpp b/src/shader_recompiler/frontend/ir/opcodes.cpp
index 24d024ad7..9e4d9e65a 100644
--- a/src/shader_recompiler/frontend/ir/opcodes.cpp
+++ b/src/shader_recompiler/frontend/ir/opcodes.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string_view>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/frontend/ir/opcodes.h"
diff --git a/src/shader_recompiler/frontend/ir/opcodes.h b/src/shader_recompiler/frontend/ir/opcodes.h
index 9ab108292..752879a18 100644
--- a/src/shader_recompiler/frontend/ir/opcodes.h
+++ b/src/shader_recompiler/frontend/ir/opcodes.h
@@ -1,12 +1,10 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <array>
-#include <string_view>
#include <fmt/format.h>
@@ -105,6 +103,6 @@ struct fmt::formatter<Shader::IR::Opcode> {
}
template <typename FormatContext>
auto format(const Shader::IR::Opcode& op, FormatContext& ctx) {
- return format_to(ctx.out(), "{}", Shader::IR::NameOf(op));
+ return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(op));
}
};
diff --git a/src/shader_recompiler/frontend/ir/opcodes.inc b/src/shader_recompiler/frontend/ir/opcodes.inc
index efb6bfac3..86410ddfc 100644
--- a/src/shader_recompiler/frontend/ir/opcodes.inc
+++ b/src/shader_recompiler/frontend/ir/opcodes.inc
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
// opcode name, return type, arg1 type, arg2 type, arg3 type, arg4 type, arg4 type, ...
OPCODE(Phi, Opaque, )
diff --git a/src/shader_recompiler/frontend/ir/patch.cpp b/src/shader_recompiler/frontend/ir/patch.cpp
index 4c956a970..b0707d85a 100644
--- a/src/shader_recompiler/frontend/ir/patch.cpp
+++ b/src/shader_recompiler/frontend/ir/patch.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/exception.h"
#include "shader_recompiler/frontend/ir/patch.h"
diff --git a/src/shader_recompiler/frontend/ir/patch.h b/src/shader_recompiler/frontend/ir/patch.h
index 6d66ff0d6..1e37c8eb6 100644
--- a/src/shader_recompiler/frontend/ir/patch.h
+++ b/src/shader_recompiler/frontend/ir/patch.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/ir/post_order.cpp b/src/shader_recompiler/frontend/ir/post_order.cpp
index 16bc44101..f526615fc 100644
--- a/src/shader_recompiler/frontend/ir/post_order.cpp
+++ b/src/shader_recompiler/frontend/ir/post_order.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
diff --git a/src/shader_recompiler/frontend/ir/post_order.h b/src/shader_recompiler/frontend/ir/post_order.h
index 07bfbadc3..d93071b07 100644
--- a/src/shader_recompiler/frontend/ir/post_order.h
+++ b/src/shader_recompiler/frontend/ir/post_order.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/ir/pred.h b/src/shader_recompiler/frontend/ir/pred.h
index 4e7f32423..a77c1e2a7 100644
--- a/src/shader_recompiler/frontend/ir/pred.h
+++ b/src/shader_recompiler/frontend/ir/pred.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/ir/program.cpp b/src/shader_recompiler/frontend/ir/program.cpp
index 3fc06f855..bda56ff46 100644
--- a/src/shader_recompiler/frontend/ir/program.cpp
+++ b/src/shader_recompiler/frontend/ir/program.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <map>
#include <string>
diff --git a/src/shader_recompiler/frontend/ir/program.h b/src/shader_recompiler/frontend/ir/program.h
index ebcaa8bc2..6b4a05c59 100644
--- a/src/shader_recompiler/frontend/ir/program.h
+++ b/src/shader_recompiler/frontend/ir/program.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/ir/reg.h b/src/shader_recompiler/frontend/ir/reg.h
index a4b635792..f7cb716a9 100644
--- a/src/shader_recompiler/frontend/ir/reg.h
+++ b/src/shader_recompiler/frontend/ir/reg.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/ir/type.cpp b/src/shader_recompiler/frontend/ir/type.cpp
index f28341bfe..32657cb05 100644
--- a/src/shader_recompiler/frontend/ir/type.cpp
+++ b/src/shader_recompiler/frontend/ir/type.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <string>
diff --git a/src/shader_recompiler/frontend/ir/type.h b/src/shader_recompiler/frontend/ir/type.h
index 294b230c4..04c8c4ddb 100644
--- a/src/shader_recompiler/frontend/ir/type.h
+++ b/src/shader_recompiler/frontend/ir/type.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/ir/value.cpp b/src/shader_recompiler/frontend/ir/value.cpp
index d365ea1bc..346169328 100644
--- a/src/shader_recompiler/frontend/ir/value.cpp
+++ b/src/shader_recompiler/frontend/ir/value.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include "shader_recompiler/frontend/ir/opcodes.h"
#include "shader_recompiler/frontend/ir/value.h"
namespace Shader::IR {
diff --git a/src/shader_recompiler/frontend/ir/value.h b/src/shader_recompiler/frontend/ir/value.h
index 947579852..1a2e4ccb6 100644
--- a/src/shader_recompiler/frontend/ir/value.h
+++ b/src/shader_recompiler/frontend/ir/value.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -179,9 +178,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/control_flow.cpp b/src/shader_recompiler/frontend/maxwell/control_flow.cpp
index efe457baa..6939692cd 100644
--- a/src/shader_recompiler/frontend/maxwell/control_flow.cpp
+++ b/src/shader_recompiler/frontend/maxwell/control_flow.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
diff --git a/src/shader_recompiler/frontend/maxwell/control_flow.h b/src/shader_recompiler/frontend/maxwell/control_flow.h
index a6bd3e196..1ce45b3a5 100644
--- a/src/shader_recompiler/frontend/maxwell/control_flow.h
+++ b/src/shader_recompiler/frontend/maxwell/control_flow.h
@@ -1,10 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <compare>
#include <optional>
#include <span>
#include <string>
@@ -15,6 +13,7 @@
#include "shader_recompiler/environment.h"
#include "shader_recompiler/frontend/ir/condition.h"
+#include "shader_recompiler/frontend/ir/reg.h"
#include "shader_recompiler/frontend/maxwell/instruction.h"
#include "shader_recompiler/frontend/maxwell/location.h"
#include "shader_recompiler/frontend/maxwell/opcodes.h"
@@ -59,7 +58,7 @@ public:
[[nodiscard]] Stack Remove(Token token) const;
private:
- boost::container::small_vector<StackEntry, 3> entries;
+ std::vector<StackEntry> entries;
};
struct IndirectBranch {
diff --git a/src/shader_recompiler/frontend/maxwell/decode.cpp b/src/shader_recompiler/frontend/maxwell/decode.cpp
index 972f677dc..455c91470 100644
--- a/src/shader_recompiler/frontend/maxwell/decode.cpp
+++ b/src/shader_recompiler/frontend/maxwell/decode.cpp
@@ -1,12 +1,10 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <bit>
#include <memory>
-#include <string_view>
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
diff --git a/src/shader_recompiler/frontend/maxwell/decode.h b/src/shader_recompiler/frontend/maxwell/decode.h
index b4f080fd7..fc2fc016a 100644
--- a/src/shader_recompiler/frontend/maxwell/decode.h
+++ b/src/shader_recompiler/frontend/maxwell/decode.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/maxwell/indirect_branch_table_track.cpp b/src/shader_recompiler/frontend/maxwell/indirect_branch_table_track.cpp
index 008625cb3..712aefaa0 100644
--- a/src/shader_recompiler/frontend/maxwell/indirect_branch_table_track.cpp
+++ b/src/shader_recompiler/frontend/maxwell/indirect_branch_table_track.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <optional>
diff --git a/src/shader_recompiler/frontend/maxwell/indirect_branch_table_track.h b/src/shader_recompiler/frontend/maxwell/indirect_branch_table_track.h
index eee5102fa..9a3afd19c 100644
--- a/src/shader_recompiler/frontend/maxwell/indirect_branch_table_track.h
+++ b/src/shader_recompiler/frontend/maxwell/indirect_branch_table_track.h
@@ -1,12 +1,10 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
-#include "common/bit_field.h"
#include "common/common_types.h"
#include "shader_recompiler/environment.h"
#include "shader_recompiler/frontend/ir/reg.h"
diff --git a/src/shader_recompiler/frontend/maxwell/instruction.h b/src/shader_recompiler/frontend/maxwell/instruction.h
index 743d68d61..d86c8cd34 100644
--- a/src/shader_recompiler/frontend/maxwell/instruction.h
+++ b/src/shader_recompiler/frontend/maxwell/instruction.h
@@ -1,13 +1,11 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/bit_field.h"
#include "common/common_types.h"
#include "shader_recompiler/frontend/ir/flow_test.h"
-#include "shader_recompiler/frontend/ir/reg.h"
namespace Shader::Maxwell {
diff --git a/src/shader_recompiler/frontend/maxwell/location.h b/src/shader_recompiler/frontend/maxwell/location.h
index 26d29eae2..0c0477e2d 100644
--- a/src/shader_recompiler/frontend/maxwell/location.h
+++ b/src/shader_recompiler/frontend/maxwell/location.h
@@ -1,12 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <compare>
-#include <iterator>
-
#include <fmt/format.h>
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/maxwell.inc b/src/shader_recompiler/frontend/maxwell/maxwell.inc
index 2fee591bb..495b3e9f1 100644
--- a/src/shader_recompiler/frontend/maxwell/maxwell.inc
+++ b/src/shader_recompiler/frontend/maxwell/maxwell.inc
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
INST(AL2P, "AL2P", "1110 1111 1010 0---")
INST(ALD, "ALD", "1110 1111 1101 1---")
diff --git a/src/shader_recompiler/frontend/maxwell/opcodes.cpp b/src/shader_recompiler/frontend/maxwell/opcodes.cpp
index ccc40c20c..2d5c4d79e 100644
--- a/src/shader_recompiler/frontend/maxwell/opcodes.cpp
+++ b/src/shader_recompiler/frontend/maxwell/opcodes.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
diff --git a/src/shader_recompiler/frontend/maxwell/opcodes.h b/src/shader_recompiler/frontend/maxwell/opcodes.h
index cd574f29d..72dd143c2 100644
--- a/src/shader_recompiler/frontend/maxwell/opcodes.h
+++ b/src/shader_recompiler/frontend/maxwell/opcodes.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -25,6 +24,6 @@ struct fmt::formatter<Shader::Maxwell::Opcode> {
}
template <typename FormatContext>
auto format(const Shader::Maxwell::Opcode& opcode, FormatContext& ctx) {
- return format_to(ctx.out(), "{}", NameOf(opcode));
+ return fmt::format_to(ctx.out(), "{}", NameOf(opcode));
}
};
diff --git a/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp b/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp
index 69eeaa3e6..ce42475d4 100644
--- a/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp
+++ b/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <memory>
@@ -8,7 +7,6 @@
#include <unordered_map>
#include <utility>
#include <vector>
-#include <version>
#include <fmt/format.h>
@@ -17,7 +15,6 @@
#include "shader_recompiler/environment.h"
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
-#include "shader_recompiler/frontend/maxwell/decode.h"
#include "shader_recompiler/frontend/maxwell/structured_control_flow.h"
#include "shader_recompiler/frontend/maxwell/translate/translate.h"
#include "shader_recompiler/host_translate_info.h"
@@ -967,9 +964,9 @@ private:
demote_endif_node.type = Type::EndIf;
demote_endif_node.data.end_if.merge = return_block_it->data.block;
- asl.insert(return_block_it, demote_endif_node);
- asl.insert(return_block_it, demote_node);
- asl.insert(return_block_it, demote_if_node);
+ const auto next_it_1 = asl.insert(return_block_it, demote_endif_node);
+ const auto next_it_2 = asl.insert(next_it_1, demote_node);
+ asl.insert(next_it_2, demote_if_node);
}
ObjectPool<Statement>& stmt_pool;
@@ -978,13 +975,7 @@ private:
Environment& env;
IR::AbstractSyntaxList& syntax_list;
bool uses_demote_to_helper{};
-
-// TODO: C++20 Remove this when all compilers support constexpr std::vector
-#if __cpp_lib_constexpr_vector >= 201907
- static constexpr Flow::Block dummy_flow_block;
-#else
const Flow::Block dummy_flow_block;
-#endif
};
} // Anonymous namespace
diff --git a/src/shader_recompiler/frontend/maxwell/structured_control_flow.h b/src/shader_recompiler/frontend/maxwell/structured_control_flow.h
index e38158da3..15c1185ed 100644
--- a/src/shader_recompiler/frontend/maxwell/structured_control_flow.h
+++ b/src/shader_recompiler/frontend/maxwell/structured_control_flow.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/atomic_operations_global_memory.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/atomic_operations_global_memory.cpp
index d9f999e05..a6be7c97e 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/atomic_operations_global_memory.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/atomic_operations_global_memory.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/atomic_operations_shared_memory.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/atomic_operations_shared_memory.cpp
index 8b974621e..5ce911247 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/atomic_operations_shared_memory.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/atomic_operations_shared_memory.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/attribute_memory_to_physical.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/attribute_memory_to_physical.cpp
index fb3f00d3f..f6ff160c9 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/attribute_memory_to_physical.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/attribute_memory_to_physical.cpp
@@ -1,10 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
-#include "shader_recompiler/frontend/maxwell/opcodes.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
namespace Shader::Maxwell {
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/barrier_operations.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/barrier_operations.cpp
index 86e433e41..fb5368c7b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/barrier_operations.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/barrier_operations.cpp
@@ -1,11 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
-#include "shader_recompiler/frontend/ir/modifiers.h"
-#include "shader_recompiler/frontend/maxwell/opcodes.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
namespace Shader::Maxwell {
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/bitfield_extract.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/bitfield_extract.cpp
index 9d5a87e52..8d8a90c9b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/bitfield_extract.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/bitfield_extract.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/bitfield_insert.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/bitfield_insert.cpp
index 1e1ec2119..e03ccc1ad 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/bitfield_insert.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/bitfield_insert.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/branch_indirect.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/branch_indirect.cpp
index 371c0e0f7..50aa16049 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/branch_indirect.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/branch_indirect.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/common_encoding.h b/src/shader_recompiler/frontend/maxwell/translate/impl/common_encoding.h
index fd73f656c..b43bb01b8 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/common_encoding.h
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/common_encoding.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/common_funcs.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/common_funcs.cpp
index 20458d2ad..3fd2c616d 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/common_funcs.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/common_funcs.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/frontend/maxwell/translate/impl/common_funcs.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/common_funcs.h b/src/shader_recompiler/frontend/maxwell/translate/impl/common_funcs.h
index 214d0af3c..25b7e12e6 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/common_funcs.h
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/common_funcs.h
@@ -1,10 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include "common/common_types.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
namespace Shader::Maxwell {
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/condition_code_set.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/condition_code_set.cpp
index 420f2fb94..4bf151499 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/condition_code_set.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/condition_code_set.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/double_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/double_add.cpp
index 5a1b3a8fc..b24ac80e0 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/double_add.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/double_add.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/double_compare_and_set.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/double_compare_and_set.cpp
index 1173192e4..6f040c45c 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/double_compare_and_set.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/double_compare_and_set.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/double_fused_multiply_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/double_fused_multiply_add.cpp
index f66097014..b43a3a2e6 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/double_fused_multiply_add.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/double_fused_multiply_add.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/double_min_max.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/double_min_max.cpp
index 6b551847c..59a138eef 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/double_min_max.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/double_min_max.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/double_multiply.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/double_multiply.cpp
index c0159fb65..a3dcdb6d7 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/double_multiply.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/double_multiply.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/double_set_predicate.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/double_set_predicate.cpp
index b8e74ee44..24859f5b8 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/double_set_predicate.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/double_set_predicate.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
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 c2443c886..c1179d9aa 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/exit_program.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/exit_program.cpp
@@ -1,9 +1,7 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
-#include "shader_recompiler/exception.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
namespace Shader::Maxwell {
@@ -12,9 +10,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/find_leading_one.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/find_leading_one.cpp
index f0cb25d61..a920bfcb2 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/find_leading_one.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/find_leading_one.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_add.cpp
index b8c89810c..7350b6a82 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_add.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_add.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_compare.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_compare.cpp
index 7127ebf54..b26a68766 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_compare.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_compare.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_compare_and_set.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_compare_and_set.cpp
index eece4f28f..8f253ff6e 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_compare_and_set.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_compare_and_set.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_floating_point.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_floating_point.cpp
index 02ab023c1..7f3dccc52 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_floating_point.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_floating_point.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/frontend/maxwell/translate/impl/common_encoding.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_integer.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_integer.cpp
index 92b1ce015..4942878b9 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_integer.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_integer.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <limits>
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_fused_multiply_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_fused_multiply_add.cpp
index fa2a7807b..fb9f40b82 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_fused_multiply_add.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_fused_multiply_add.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_min_max.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_min_max.cpp
index c0d6ee5af..125ade769 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_min_max.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_min_max.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multi_function.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multi_function.cpp
index 2f8605619..64b213ebf 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multi_function.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multi_function.cpp
@@ -1,11 +1,9 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
-#include "shader_recompiler/frontend/maxwell/opcodes.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
namespace Shader::Maxwell {
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multiply.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multiply.cpp
index 06226b7ce..fc8c7174b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multiply.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multiply.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_range_reduction.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_range_reduction.cpp
index f91b93fad..96e3e68e0 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_range_reduction.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_range_reduction.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_set_predicate.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_set_predicate.cpp
index 5f93a1513..cbdaafc7b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_set_predicate.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_set_predicate.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp
index 7550a8d4c..ef4ffa54b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_add.cpp
index f2738a93b..3e810b67f 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_add.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_add.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_fused_multiply_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_fused_multiply_add.cpp
index fd7986701..a75a2d84b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_fused_multiply_add.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_fused_multiply_add.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.cpp
index 0dbeb7f56..014217ece 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.h b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.h
index 59da56a7e..3283b85bd 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.h
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.h
@@ -1,13 +1,10 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
-#include "shader_recompiler/frontend/maxwell/translate/impl/common_encoding.h"
-#include "shader_recompiler/frontend/maxwell/translate/impl/common_funcs.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
namespace Shader::Maxwell {
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_multiply.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_multiply.cpp
index 3f548ce76..c83b5649a 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_multiply.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_multiply.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_set.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_set.cpp
index cca5b831f..c3080da25 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_set.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_set.cpp
@@ -1,7 +1,7 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include "shader_recompiler/frontend/maxwell/translate/impl/common_funcs.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.h"
namespace Shader::Maxwell {
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_set_predicate.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_set_predicate.cpp
index b3931dae3..a08979e8d 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_set_predicate.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_set_predicate.cpp
@@ -1,7 +1,7 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include "shader_recompiler/frontend/maxwell/translate/impl/common_funcs.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/half_floating_point_helper.h"
namespace Shader::Maxwell {
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/impl.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/impl.cpp
index b446aae0e..b0ebc75f7 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/impl.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/impl.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/impl.h b/src/shader_recompiler/frontend/maxwell/translate/impl/impl.h
index 335e4f24f..adf7cad06 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/impl.h
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/impl.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_add.cpp
index 8ffd84867..49f70451a 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_add.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_add.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_add_three_input.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_add_three_input.cpp
index 040cfc10f..3d9877359 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_add_three_input.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_add_three_input.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_compare.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_compare.cpp
index ba6e01926..ac1b405c5 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_compare.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_compare.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_compare_and_set.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_compare_and_set.cpp
index 8ce1aee04..a708f8b91 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_compare_and_set.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_compare_and_set.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_floating_point_conversion.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_floating_point_conversion.cpp
index 0b8119ddd..a2dc0f4a6 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_floating_point_conversion.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_floating_point_conversion.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_funnel_shift.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_funnel_shift.cpp
index 5feefc0ce..442365a26 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_funnel_shift.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_funnel_shift.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_minimum_maximum.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_minimum_maximum.cpp
index 1badbacc4..c3f6b0b7d 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_minimum_maximum.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_minimum_maximum.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_popcount.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_popcount.cpp
index 5ece7678d..2a535f387 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_popcount.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_popcount.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_scaled_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_scaled_add.cpp
index 044671943..4a3f0772f 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_scaled_add.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_scaled_add.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_set_predicate.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_set_predicate.cpp
index bee10e5b9..c5d5221a0 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_set_predicate.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_set_predicate.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_left.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_left.cpp
index 20af68852..5779cfbdd 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_left.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_left.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_right.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_right.cpp
index be00bb605..2dccd2ca6 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_right.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_right.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_short_multiply_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_short_multiply_add.cpp
index 2932cdc42..a96f8707e 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_short_multiply_add.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_short_multiply_add.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_to_integer_conversion.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_to_integer_conversion.cpp
index 53e8d8923..7484621d0 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_to_integer_conversion.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_to_integer_conversion.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/internal_stage_buffer_entry_read.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/internal_stage_buffer_entry_read.cpp
index 9b85f8059..7025f14a2 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/internal_stage_buffer_entry_read.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/internal_stage_buffer_entry_read.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/load_constant.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/load_constant.cpp
index 2300088e3..7e12c089b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/load_constant.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/load_constant.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
@@ -11,10 +10,20 @@ namespace Shader::Maxwell {
using namespace LDC;
namespace {
std::pair<IR::U32, IR::U32> Slot(IR::IREmitter& ir, Mode mode, const IR::U32& imm_index,
- const IR::U32& reg, const IR::U32& imm) {
+ const IR::U32& reg, const IR::U32& imm_offset) {
switch (mode) {
case Mode::Default:
- return {imm_index, ir.IAdd(reg, imm)};
+ return {imm_index, ir.IAdd(reg, imm_offset)};
+ case Mode::IS: {
+ // Segmented addressing mode
+ // Ra+imm_offset points into a flat mapping of const buffer
+ // address space
+ const IR::U32 address{ir.IAdd(reg, imm_offset)};
+ const IR::U32 index{ir.BitFieldExtract(address, ir.Imm32(16), ir.Imm32(16))};
+ const IR::U32 offset{ir.BitFieldExtract(address, ir.Imm32(0), ir.Imm32(16))};
+
+ return {ir.IAdd(index, imm_index), offset};
+ }
default:
break;
}
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/load_constant.h b/src/shader_recompiler/frontend/maxwell/translate/impl/load_constant.h
index 3074ea0e3..0df13b7c8 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/load_constant.h
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/load_constant.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/load_effective_address.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/load_effective_address.cpp
index 4a0f04e47..8361c8ab1 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/load_effective_address.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/load_effective_address.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp
index 924fb7a40..e3745ce08 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp
@@ -1,12 +1,10 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
-#include "shader_recompiler/frontend/maxwell/opcodes.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
namespace Shader::Maxwell {
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_local_shared.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_local_shared.cpp
index d2a1dbf61..a198b2b76 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_local_shared.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_local_shared.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_memory.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_memory.cpp
index 36c5cff2f..8f2cf897d 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_memory.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_memory.cpp
@@ -1,11 +1,9 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
-#include "shader_recompiler/frontend/maxwell/opcodes.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
namespace Shader::Maxwell {
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation.cpp
index 92cd27ed4..4e0ff897c 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input.cpp
index e0fe47912..a4ec25844 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
@@ -13,59 +12,535 @@ namespace {
// Emulate GPU's LOP3.LUT (three-input logic op with 8-bit truth table)
IR::U32 ApplyLUT(IR::IREmitter& ir, const IR::U32& a, const IR::U32& b, const IR::U32& c,
u64 ttbl) {
- IR::U32 r{ir.Imm32(0)};
- const IR::U32 not_a{ir.BitwiseNot(a)};
- const IR::U32 not_b{ir.BitwiseNot(b)};
- const IR::U32 not_c{ir.BitwiseNot(c)};
- if (ttbl & 0x01) {
- // r |= ~a & ~b & ~c;
- const auto lhs{ir.BitwiseAnd(not_a, not_b)};
- const auto rhs{ir.BitwiseAnd(lhs, not_c)};
- r = ir.BitwiseOr(r, rhs);
+ switch (ttbl) {
+ // generated code, do not edit manually
+ case 0:
+ return ir.Imm32(0);
+ case 1:
+ return ir.BitwiseNot(ir.BitwiseOr(a, ir.BitwiseOr(b, c)));
+ case 2:
+ return ir.BitwiseAnd(c, ir.BitwiseNot(ir.BitwiseOr(a, b)));
+ case 3:
+ return ir.BitwiseNot(ir.BitwiseOr(a, b));
+ case 4:
+ return ir.BitwiseAnd(b, ir.BitwiseNot(ir.BitwiseOr(a, c)));
+ case 5:
+ return ir.BitwiseNot(ir.BitwiseOr(a, c));
+ case 6:
+ return ir.BitwiseAnd(ir.BitwiseNot(a), ir.BitwiseXor(b, c));
+ case 7:
+ return ir.BitwiseNot(ir.BitwiseOr(a, ir.BitwiseAnd(b, c)));
+ case 8:
+ return ir.BitwiseAnd(ir.BitwiseAnd(b, c), ir.BitwiseNot(a));
+ case 9:
+ return ir.BitwiseNot(ir.BitwiseOr(a, ir.BitwiseXor(b, c)));
+ case 10:
+ return ir.BitwiseAnd(c, ir.BitwiseNot(a));
+ case 11:
+ return ir.BitwiseAnd(ir.BitwiseNot(a), ir.BitwiseOr(c, ir.BitwiseNot(b)));
+ case 12:
+ return ir.BitwiseAnd(b, ir.BitwiseNot(a));
+ case 13:
+ return ir.BitwiseAnd(ir.BitwiseNot(a), ir.BitwiseOr(b, ir.BitwiseNot(c)));
+ case 14:
+ return ir.BitwiseAnd(ir.BitwiseNot(a), ir.BitwiseOr(b, c));
+ case 15:
+ return ir.BitwiseNot(a);
+ case 16:
+ return ir.BitwiseAnd(a, ir.BitwiseNot(ir.BitwiseOr(b, c)));
+ case 17:
+ return ir.BitwiseNot(ir.BitwiseOr(b, c));
+ case 18:
+ return ir.BitwiseAnd(ir.BitwiseNot(b), ir.BitwiseXor(a, c));
+ case 19:
+ return ir.BitwiseNot(ir.BitwiseOr(b, ir.BitwiseAnd(a, c)));
+ case 20:
+ return ir.BitwiseAnd(ir.BitwiseNot(c), ir.BitwiseXor(a, b));
+ case 21:
+ return ir.BitwiseNot(ir.BitwiseOr(c, ir.BitwiseAnd(a, b)));
+ case 22:
+ return ir.BitwiseXor(ir.BitwiseOr(a, b), ir.BitwiseOr(c, ir.BitwiseAnd(a, b)));
+ case 23:
+ return ir.BitwiseXor(ir.BitwiseAnd(ir.BitwiseXor(a, b), ir.BitwiseXor(a, c)),
+ ir.BitwiseNot(a));
+ case 24:
+ return ir.BitwiseAnd(ir.BitwiseXor(a, b), ir.BitwiseXor(a, c));
+ case 25:
+ return ir.BitwiseNot(ir.BitwiseOr(ir.BitwiseAnd(a, b), ir.BitwiseXor(b, c)));
+ case 26:
+ return ir.BitwiseAnd(ir.BitwiseOr(c, ir.BitwiseNot(b)), ir.BitwiseXor(a, c));
+ case 27:
+ return ir.BitwiseXor(ir.BitwiseOr(a, ir.BitwiseNot(c)), ir.BitwiseOr(b, c));
+ case 28:
+ return ir.BitwiseAnd(ir.BitwiseOr(b, ir.BitwiseNot(c)), ir.BitwiseXor(a, b));
+ case 29:
+ return ir.BitwiseXor(ir.BitwiseOr(a, ir.BitwiseNot(b)), ir.BitwiseOr(b, c));
+ case 30:
+ return ir.BitwiseXor(a, ir.BitwiseOr(b, c));
+ case 31:
+ return ir.BitwiseNot(ir.BitwiseAnd(a, ir.BitwiseOr(b, c)));
+ case 32:
+ return ir.BitwiseAnd(ir.BitwiseAnd(a, c), ir.BitwiseNot(b));
+ case 33:
+ return ir.BitwiseNot(ir.BitwiseOr(b, ir.BitwiseXor(a, c)));
+ case 34:
+ return ir.BitwiseAnd(c, ir.BitwiseNot(b));
+ case 35:
+ return ir.BitwiseAnd(ir.BitwiseNot(b), ir.BitwiseOr(c, ir.BitwiseNot(a)));
+ case 36:
+ return ir.BitwiseAnd(ir.BitwiseXor(a, b), ir.BitwiseXor(b, c));
+ case 37:
+ return ir.BitwiseNot(ir.BitwiseOr(ir.BitwiseAnd(a, b), ir.BitwiseXor(a, c)));
+ case 38:
+ return ir.BitwiseAnd(ir.BitwiseOr(c, ir.BitwiseNot(a)), ir.BitwiseXor(b, c));
+ case 39:
+ return ir.BitwiseXor(ir.BitwiseOr(a, c), ir.BitwiseOr(b, ir.BitwiseNot(c)));
+ case 40:
+ return ir.BitwiseAnd(c, ir.BitwiseXor(a, b));
+ case 41:
+ return ir.BitwiseXor(ir.BitwiseOr(a, b),
+ ir.BitwiseOr(ir.BitwiseAnd(a, b), ir.BitwiseNot(c)));
+ case 42:
+ return ir.BitwiseAnd(c, ir.BitwiseNot(ir.BitwiseAnd(a, b)));
+ case 43:
+ return ir.BitwiseXor(ir.BitwiseOr(a, ir.BitwiseNot(c)),
+ ir.BitwiseOr(b, ir.BitwiseXor(a, c)));
+ case 44:
+ return ir.BitwiseAnd(ir.BitwiseOr(b, c), ir.BitwiseXor(a, b));
+ case 45:
+ return ir.BitwiseXor(a, ir.BitwiseOr(b, ir.BitwiseNot(c)));
+ case 46:
+ return ir.BitwiseXor(ir.BitwiseAnd(a, b), ir.BitwiseOr(b, c));
+ case 47:
+ return ir.BitwiseOr(ir.BitwiseAnd(c, ir.BitwiseNot(b)), ir.BitwiseNot(a));
+ case 48:
+ return ir.BitwiseAnd(a, ir.BitwiseNot(b));
+ case 49:
+ return ir.BitwiseAnd(ir.BitwiseNot(b), ir.BitwiseOr(a, ir.BitwiseNot(c)));
+ case 50:
+ return ir.BitwiseAnd(ir.BitwiseNot(b), ir.BitwiseOr(a, c));
+ case 51:
+ return ir.BitwiseNot(b);
+ case 52:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, ir.BitwiseNot(c)), ir.BitwiseXor(a, b));
+ case 53:
+ return ir.BitwiseXor(ir.BitwiseOr(a, c), ir.BitwiseOr(b, ir.BitwiseNot(a)));
+ case 54:
+ return ir.BitwiseXor(b, ir.BitwiseOr(a, c));
+ case 55:
+ return ir.BitwiseNot(ir.BitwiseAnd(b, ir.BitwiseOr(a, c)));
+ case 56:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, c), ir.BitwiseXor(a, b));
+ case 57:
+ return ir.BitwiseXor(b, ir.BitwiseOr(a, ir.BitwiseNot(c)));
+ case 58:
+ return ir.BitwiseXor(ir.BitwiseAnd(a, b), ir.BitwiseOr(a, c));
+ case 59:
+ return ir.BitwiseOr(ir.BitwiseAnd(c, ir.BitwiseNot(a)), ir.BitwiseNot(b));
+ case 60:
+ return ir.BitwiseXor(a, b);
+ case 61:
+ return ir.BitwiseOr(ir.BitwiseNot(ir.BitwiseOr(a, c)), ir.BitwiseXor(a, b));
+ case 62:
+ return ir.BitwiseOr(ir.BitwiseAnd(c, ir.BitwiseNot(a)), ir.BitwiseXor(a, b));
+ case 63:
+ return ir.BitwiseNot(ir.BitwiseAnd(a, b));
+ case 64:
+ return ir.BitwiseAnd(ir.BitwiseAnd(a, b), ir.BitwiseNot(c));
+ case 65:
+ return ir.BitwiseNot(ir.BitwiseOr(c, ir.BitwiseXor(a, b)));
+ case 66:
+ return ir.BitwiseAnd(ir.BitwiseXor(a, c), ir.BitwiseXor(b, c));
+ case 67:
+ return ir.BitwiseNot(ir.BitwiseOr(ir.BitwiseAnd(a, c), ir.BitwiseXor(a, b)));
+ case 68:
+ return ir.BitwiseAnd(b, ir.BitwiseNot(c));
+ case 69:
+ return ir.BitwiseAnd(ir.BitwiseNot(c), ir.BitwiseOr(b, ir.BitwiseNot(a)));
+ case 70:
+ return ir.BitwiseAnd(ir.BitwiseOr(b, ir.BitwiseNot(a)), ir.BitwiseXor(b, c));
+ case 71:
+ return ir.BitwiseXor(ir.BitwiseOr(a, b), ir.BitwiseOr(c, ir.BitwiseNot(b)));
+ case 72:
+ return ir.BitwiseAnd(b, ir.BitwiseXor(a, c));
+ case 73:
+ return ir.BitwiseXor(ir.BitwiseOr(a, c),
+ ir.BitwiseOr(ir.BitwiseAnd(a, c), ir.BitwiseNot(b)));
+ case 74:
+ return ir.BitwiseAnd(ir.BitwiseOr(b, c), ir.BitwiseXor(a, c));
+ case 75:
+ return ir.BitwiseXor(a, ir.BitwiseOr(c, ir.BitwiseNot(b)));
+ case 76:
+ return ir.BitwiseAnd(b, ir.BitwiseNot(ir.BitwiseAnd(a, c)));
+ case 77:
+ return ir.BitwiseXor(ir.BitwiseOr(a, ir.BitwiseNot(b)),
+ ir.BitwiseOr(c, ir.BitwiseXor(a, b)));
+ case 78:
+ return ir.BitwiseXor(ir.BitwiseAnd(a, c), ir.BitwiseOr(b, c));
+ case 79:
+ return ir.BitwiseOr(ir.BitwiseAnd(b, ir.BitwiseNot(c)), ir.BitwiseNot(a));
+ case 80:
+ return ir.BitwiseAnd(a, ir.BitwiseNot(c));
+ case 81:
+ return ir.BitwiseAnd(ir.BitwiseNot(c), ir.BitwiseOr(a, ir.BitwiseNot(b)));
+ case 82:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, ir.BitwiseNot(b)), ir.BitwiseXor(a, c));
+ case 83:
+ return ir.BitwiseXor(ir.BitwiseOr(a, b), ir.BitwiseOr(c, ir.BitwiseNot(a)));
+ case 84:
+ return ir.BitwiseAnd(ir.BitwiseNot(c), ir.BitwiseOr(a, b));
+ case 85:
+ return ir.BitwiseNot(c);
+ case 86:
+ return ir.BitwiseXor(c, ir.BitwiseOr(a, b));
+ case 87:
+ return ir.BitwiseNot(ir.BitwiseAnd(c, ir.BitwiseOr(a, b)));
+ case 88:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, b), ir.BitwiseXor(a, c));
+ case 89:
+ return ir.BitwiseXor(c, ir.BitwiseOr(a, ir.BitwiseNot(b)));
+ case 90:
+ return ir.BitwiseXor(a, c);
+ case 91:
+ return ir.BitwiseOr(ir.BitwiseNot(ir.BitwiseOr(a, b)), ir.BitwiseXor(a, c));
+ case 92:
+ return ir.BitwiseXor(ir.BitwiseAnd(a, c), ir.BitwiseOr(a, b));
+ case 93:
+ return ir.BitwiseOr(ir.BitwiseAnd(b, ir.BitwiseNot(a)), ir.BitwiseNot(c));
+ case 94:
+ return ir.BitwiseOr(ir.BitwiseAnd(b, ir.BitwiseNot(a)), ir.BitwiseXor(a, c));
+ case 95:
+ return ir.BitwiseNot(ir.BitwiseAnd(a, c));
+ case 96:
+ return ir.BitwiseAnd(a, ir.BitwiseXor(b, c));
+ case 97:
+ return ir.BitwiseXor(ir.BitwiseOr(b, c),
+ ir.BitwiseOr(ir.BitwiseAnd(b, c), ir.BitwiseNot(a)));
+ case 98:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, c), ir.BitwiseXor(b, c));
+ case 99:
+ return ir.BitwiseXor(b, ir.BitwiseOr(c, ir.BitwiseNot(a)));
+ case 100:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, b), ir.BitwiseXor(b, c));
+ case 101:
+ return ir.BitwiseXor(c, ir.BitwiseOr(b, ir.BitwiseNot(a)));
+ case 102:
+ return ir.BitwiseXor(b, c);
+ case 103:
+ return ir.BitwiseOr(ir.BitwiseNot(ir.BitwiseOr(a, b)), ir.BitwiseXor(b, c));
+ case 104:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, b), ir.BitwiseXor(c, ir.BitwiseAnd(a, b)));
+ case 105:
+ return ir.BitwiseXor(ir.BitwiseNot(a), ir.BitwiseXor(b, c));
+ case 106:
+ return ir.BitwiseXor(c, ir.BitwiseAnd(a, b));
+ case 107:
+ return ir.BitwiseXor(ir.BitwiseAnd(c, ir.BitwiseOr(a, b)),
+ ir.BitwiseXor(a, ir.BitwiseNot(b)));
+ case 108:
+ return ir.BitwiseXor(b, ir.BitwiseAnd(a, c));
+ case 109:
+ return ir.BitwiseXor(ir.BitwiseAnd(b, ir.BitwiseOr(a, c)),
+ ir.BitwiseXor(a, ir.BitwiseNot(c)));
+ case 110:
+ return ir.BitwiseOr(ir.BitwiseAnd(b, ir.BitwiseNot(a)), ir.BitwiseXor(b, c));
+ case 111:
+ return ir.BitwiseOr(ir.BitwiseNot(a), ir.BitwiseXor(b, c));
+ case 112:
+ return ir.BitwiseAnd(a, ir.BitwiseNot(ir.BitwiseAnd(b, c)));
+ case 113:
+ return ir.BitwiseXor(ir.BitwiseOr(b, ir.BitwiseNot(a)),
+ ir.BitwiseOr(c, ir.BitwiseXor(a, b)));
+ case 114:
+ return ir.BitwiseXor(ir.BitwiseAnd(b, c), ir.BitwiseOr(a, c));
+ case 115:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, ir.BitwiseNot(c)), ir.BitwiseNot(b));
+ case 116:
+ return ir.BitwiseXor(ir.BitwiseAnd(b, c), ir.BitwiseOr(a, b));
+ case 117:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, ir.BitwiseNot(b)), ir.BitwiseNot(c));
+ case 118:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, ir.BitwiseNot(b)), ir.BitwiseXor(b, c));
+ case 119:
+ return ir.BitwiseNot(ir.BitwiseAnd(b, c));
+ case 120:
+ return ir.BitwiseXor(a, ir.BitwiseAnd(b, c));
+ case 121:
+ return ir.BitwiseXor(ir.BitwiseAnd(a, ir.BitwiseOr(b, c)),
+ ir.BitwiseXor(b, ir.BitwiseNot(c)));
+ case 122:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, ir.BitwiseNot(b)), ir.BitwiseXor(a, c));
+ case 123:
+ return ir.BitwiseOr(ir.BitwiseNot(b), ir.BitwiseXor(a, c));
+ case 124:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, ir.BitwiseNot(c)), ir.BitwiseXor(a, b));
+ case 125:
+ return ir.BitwiseOr(ir.BitwiseNot(c), ir.BitwiseXor(a, b));
+ case 126:
+ return ir.BitwiseOr(ir.BitwiseXor(a, b), ir.BitwiseXor(a, c));
+ case 127:
+ return ir.BitwiseNot(ir.BitwiseAnd(a, ir.BitwiseAnd(b, c)));
+ case 128:
+ return ir.BitwiseAnd(a, ir.BitwiseAnd(b, c));
+ case 129:
+ return ir.BitwiseNot(ir.BitwiseOr(ir.BitwiseXor(a, b), ir.BitwiseXor(a, c)));
+ case 130:
+ return ir.BitwiseAnd(c, ir.BitwiseXor(a, ir.BitwiseNot(b)));
+ case 131:
+ return ir.BitwiseAnd(ir.BitwiseOr(c, ir.BitwiseNot(a)), ir.BitwiseXor(a, ir.BitwiseNot(b)));
+ case 132:
+ return ir.BitwiseAnd(b, ir.BitwiseXor(a, ir.BitwiseNot(c)));
+ case 133:
+ return ir.BitwiseAnd(ir.BitwiseOr(b, ir.BitwiseNot(a)), ir.BitwiseXor(a, ir.BitwiseNot(c)));
+ case 134:
+ return ir.BitwiseAnd(ir.BitwiseOr(b, c), ir.BitwiseXor(a, ir.BitwiseXor(b, c)));
+ case 135:
+ return ir.BitwiseXor(ir.BitwiseAnd(b, c), ir.BitwiseNot(a));
+ case 136:
+ return ir.BitwiseAnd(b, c);
+ case 137:
+ return ir.BitwiseAnd(ir.BitwiseOr(b, ir.BitwiseNot(a)), ir.BitwiseXor(b, ir.BitwiseNot(c)));
+ case 138:
+ return ir.BitwiseAnd(c, ir.BitwiseOr(b, ir.BitwiseNot(a)));
+ case 139:
+ return ir.BitwiseOr(ir.BitwiseAnd(b, c), ir.BitwiseNot(ir.BitwiseOr(a, b)));
+ case 140:
+ return ir.BitwiseAnd(b, ir.BitwiseOr(c, ir.BitwiseNot(a)));
+ case 141:
+ return ir.BitwiseOr(ir.BitwiseAnd(b, c), ir.BitwiseNot(ir.BitwiseOr(a, c)));
+ case 142:
+ return ir.BitwiseXor(a, ir.BitwiseOr(ir.BitwiseXor(a, b), ir.BitwiseXor(a, c)));
+ case 143:
+ return ir.BitwiseOr(ir.BitwiseAnd(b, c), ir.BitwiseNot(a));
+ case 144:
+ return ir.BitwiseAnd(a, ir.BitwiseXor(b, ir.BitwiseNot(c)));
+ case 145:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, ir.BitwiseNot(b)), ir.BitwiseXor(b, ir.BitwiseNot(c)));
+ case 146:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, c), ir.BitwiseXor(a, ir.BitwiseXor(b, c)));
+ case 147:
+ return ir.BitwiseXor(ir.BitwiseAnd(a, c), ir.BitwiseNot(b));
+ case 148:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, b), ir.BitwiseXor(a, ir.BitwiseXor(b, c)));
+ case 149:
+ return ir.BitwiseXor(ir.BitwiseAnd(a, b), ir.BitwiseNot(c));
+ case 150:
+ return ir.BitwiseXor(a, ir.BitwiseXor(b, c));
+ case 151:
+ return ir.BitwiseOr(ir.BitwiseNot(ir.BitwiseOr(a, b)),
+ ir.BitwiseXor(a, ir.BitwiseXor(b, c)));
+ case 152:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, b), ir.BitwiseXor(b, ir.BitwiseNot(c)));
+ case 153:
+ return ir.BitwiseXor(b, ir.BitwiseNot(c));
+ case 154:
+ return ir.BitwiseXor(c, ir.BitwiseAnd(a, ir.BitwiseNot(b)));
+ case 155:
+ return ir.BitwiseNot(ir.BitwiseAnd(ir.BitwiseOr(a, b), ir.BitwiseXor(b, c)));
+ case 156:
+ return ir.BitwiseXor(b, ir.BitwiseAnd(a, ir.BitwiseNot(c)));
+ case 157:
+ return ir.BitwiseNot(ir.BitwiseAnd(ir.BitwiseOr(a, c), ir.BitwiseXor(b, c)));
+ case 158:
+ return ir.BitwiseOr(ir.BitwiseAnd(b, c), ir.BitwiseXor(a, ir.BitwiseOr(b, c)));
+ case 159:
+ return ir.BitwiseNot(ir.BitwiseAnd(a, ir.BitwiseXor(b, c)));
+ case 160:
+ return ir.BitwiseAnd(a, c);
+ case 161:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, ir.BitwiseNot(b)), ir.BitwiseXor(a, ir.BitwiseNot(c)));
+ case 162:
+ return ir.BitwiseAnd(c, ir.BitwiseOr(a, ir.BitwiseNot(b)));
+ case 163:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, c), ir.BitwiseNot(ir.BitwiseOr(a, b)));
+ case 164:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, b), ir.BitwiseXor(a, ir.BitwiseNot(c)));
+ case 165:
+ return ir.BitwiseXor(a, ir.BitwiseNot(c));
+ case 166:
+ return ir.BitwiseXor(c, ir.BitwiseAnd(b, ir.BitwiseNot(a)));
+ case 167:
+ return ir.BitwiseNot(ir.BitwiseAnd(ir.BitwiseOr(a, b), ir.BitwiseXor(a, c)));
+ case 168:
+ return ir.BitwiseAnd(c, ir.BitwiseOr(a, b));
+ case 169:
+ return ir.BitwiseXor(ir.BitwiseNot(c), ir.BitwiseOr(a, b));
+ case 170:
+ return c;
+ case 171:
+ return ir.BitwiseOr(c, ir.BitwiseNot(ir.BitwiseOr(a, b)));
+ case 172:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, b), ir.BitwiseOr(c, ir.BitwiseNot(a)));
+ case 173:
+ return ir.BitwiseOr(ir.BitwiseAnd(b, c), ir.BitwiseXor(a, ir.BitwiseNot(c)));
+ case 174:
+ return ir.BitwiseOr(c, ir.BitwiseAnd(b, ir.BitwiseNot(a)));
+ case 175:
+ return ir.BitwiseOr(c, ir.BitwiseNot(a));
+ case 176:
+ return ir.BitwiseAnd(a, ir.BitwiseOr(c, ir.BitwiseNot(b)));
+ case 177:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, c), ir.BitwiseNot(ir.BitwiseOr(b, c)));
+ case 178:
+ return ir.BitwiseXor(b, ir.BitwiseOr(ir.BitwiseXor(a, b), ir.BitwiseXor(a, c)));
+ case 179:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, c), ir.BitwiseNot(b));
+ case 180:
+ return ir.BitwiseXor(a, ir.BitwiseAnd(b, ir.BitwiseNot(c)));
+ case 181:
+ return ir.BitwiseNot(ir.BitwiseAnd(ir.BitwiseOr(b, c), ir.BitwiseXor(a, c)));
+ case 182:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, c), ir.BitwiseXor(b, ir.BitwiseOr(a, c)));
+ case 183:
+ return ir.BitwiseNot(ir.BitwiseAnd(b, ir.BitwiseXor(a, c)));
+ case 184:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, b), ir.BitwiseOr(c, ir.BitwiseNot(b)));
+ case 185:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, c), ir.BitwiseXor(b, ir.BitwiseNot(c)));
+ case 186:
+ return ir.BitwiseOr(c, ir.BitwiseAnd(a, ir.BitwiseNot(b)));
+ case 187:
+ return ir.BitwiseOr(c, ir.BitwiseNot(b));
+ case 188:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, c), ir.BitwiseXor(a, b));
+ case 189:
+ return ir.BitwiseOr(ir.BitwiseXor(a, b), ir.BitwiseXor(a, ir.BitwiseNot(c)));
+ case 190:
+ return ir.BitwiseOr(c, ir.BitwiseXor(a, b));
+ case 191:
+ return ir.BitwiseOr(c, ir.BitwiseNot(ir.BitwiseAnd(a, b)));
+ case 192:
+ return ir.BitwiseAnd(a, b);
+ case 193:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, ir.BitwiseNot(c)), ir.BitwiseXor(a, ir.BitwiseNot(b)));
+ case 194:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, c), ir.BitwiseXor(a, ir.BitwiseNot(b)));
+ case 195:
+ return ir.BitwiseXor(a, ir.BitwiseNot(b));
+ case 196:
+ return ir.BitwiseAnd(b, ir.BitwiseOr(a, ir.BitwiseNot(c)));
+ case 197:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, b), ir.BitwiseNot(ir.BitwiseOr(a, c)));
+ case 198:
+ return ir.BitwiseXor(b, ir.BitwiseAnd(c, ir.BitwiseNot(a)));
+ case 199:
+ return ir.BitwiseNot(ir.BitwiseAnd(ir.BitwiseOr(a, c), ir.BitwiseXor(a, b)));
+ case 200:
+ return ir.BitwiseAnd(b, ir.BitwiseOr(a, c));
+ case 201:
+ return ir.BitwiseXor(ir.BitwiseNot(b), ir.BitwiseOr(a, c));
+ case 202:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, c), ir.BitwiseOr(b, ir.BitwiseNot(a)));
+ case 203:
+ return ir.BitwiseOr(ir.BitwiseAnd(b, c), ir.BitwiseXor(a, ir.BitwiseNot(b)));
+ case 204:
+ return b;
+ case 205:
+ return ir.BitwiseOr(b, ir.BitwiseNot(ir.BitwiseOr(a, c)));
+ case 206:
+ return ir.BitwiseOr(b, ir.BitwiseAnd(c, ir.BitwiseNot(a)));
+ case 207:
+ return ir.BitwiseOr(b, ir.BitwiseNot(a));
+ case 208:
+ return ir.BitwiseAnd(a, ir.BitwiseOr(b, ir.BitwiseNot(c)));
+ case 209:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, b), ir.BitwiseNot(ir.BitwiseOr(b, c)));
+ case 210:
+ return ir.BitwiseXor(a, ir.BitwiseAnd(c, ir.BitwiseNot(b)));
+ case 211:
+ return ir.BitwiseNot(ir.BitwiseAnd(ir.BitwiseOr(b, c), ir.BitwiseXor(a, b)));
+ case 212:
+ return ir.BitwiseXor(c, ir.BitwiseOr(ir.BitwiseXor(a, b), ir.BitwiseXor(a, c)));
+ case 213:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, b), ir.BitwiseNot(c));
+ case 214:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, b), ir.BitwiseXor(c, ir.BitwiseOr(a, b)));
+ case 215:
+ return ir.BitwiseNot(ir.BitwiseAnd(c, ir.BitwiseXor(a, b)));
+ case 216:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, c), ir.BitwiseOr(b, ir.BitwiseNot(c)));
+ case 217:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, b), ir.BitwiseXor(b, ir.BitwiseNot(c)));
+ case 218:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, b), ir.BitwiseXor(a, c));
+ case 219:
+ return ir.BitwiseOr(ir.BitwiseXor(a, c), ir.BitwiseXor(a, ir.BitwiseNot(b)));
+ case 220:
+ return ir.BitwiseOr(b, ir.BitwiseAnd(a, ir.BitwiseNot(c)));
+ case 221:
+ return ir.BitwiseOr(b, ir.BitwiseNot(c));
+ case 222:
+ return ir.BitwiseOr(b, ir.BitwiseXor(a, c));
+ case 223:
+ return ir.BitwiseOr(b, ir.BitwiseNot(ir.BitwiseAnd(a, c)));
+ case 224:
+ return ir.BitwiseAnd(a, ir.BitwiseOr(b, c));
+ case 225:
+ return ir.BitwiseXor(ir.BitwiseNot(a), ir.BitwiseOr(b, c));
+ case 226:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, ir.BitwiseNot(b)), ir.BitwiseOr(b, c));
+ case 227:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, c), ir.BitwiseXor(a, ir.BitwiseNot(b)));
+ case 228:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, ir.BitwiseNot(c)), ir.BitwiseOr(b, c));
+ case 229:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, b), ir.BitwiseXor(a, ir.BitwiseNot(c)));
+ case 230:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, b), ir.BitwiseXor(b, c));
+ case 231:
+ return ir.BitwiseOr(ir.BitwiseXor(a, ir.BitwiseNot(b)), ir.BitwiseXor(b, c));
+ case 232:
+ return ir.BitwiseAnd(ir.BitwiseOr(a, b), ir.BitwiseOr(c, ir.BitwiseAnd(a, b)));
+ case 233:
+ return ir.BitwiseOr(ir.BitwiseAnd(a, b),
+ ir.BitwiseXor(ir.BitwiseNot(c), ir.BitwiseOr(a, b)));
+ case 234:
+ return ir.BitwiseOr(c, ir.BitwiseAnd(a, b));
+ case 235:
+ return ir.BitwiseOr(c, ir.BitwiseXor(a, ir.BitwiseNot(b)));
+ case 236:
+ return ir.BitwiseOr(b, ir.BitwiseAnd(a, c));
+ case 237:
+ return ir.BitwiseOr(b, ir.BitwiseXor(a, ir.BitwiseNot(c)));
+ case 238:
+ return ir.BitwiseOr(b, c);
+ case 239:
+ return ir.BitwiseOr(ir.BitwiseNot(a), ir.BitwiseOr(b, c));
+ case 240:
+ return a;
+ case 241:
+ return ir.BitwiseOr(a, ir.BitwiseNot(ir.BitwiseOr(b, c)));
+ case 242:
+ return ir.BitwiseOr(a, ir.BitwiseAnd(c, ir.BitwiseNot(b)));
+ case 243:
+ return ir.BitwiseOr(a, ir.BitwiseNot(b));
+ case 244:
+ return ir.BitwiseOr(a, ir.BitwiseAnd(b, ir.BitwiseNot(c)));
+ case 245:
+ return ir.BitwiseOr(a, ir.BitwiseNot(c));
+ case 246:
+ return ir.BitwiseOr(a, ir.BitwiseXor(b, c));
+ case 247:
+ return ir.BitwiseOr(a, ir.BitwiseNot(ir.BitwiseAnd(b, c)));
+ case 248:
+ return ir.BitwiseOr(a, ir.BitwiseAnd(b, c));
+ case 249:
+ return ir.BitwiseOr(a, ir.BitwiseXor(b, ir.BitwiseNot(c)));
+ case 250:
+ return ir.BitwiseOr(a, c);
+ case 251:
+ return ir.BitwiseOr(ir.BitwiseNot(b), ir.BitwiseOr(a, c));
+ case 252:
+ return ir.BitwiseOr(a, b);
+ case 253:
+ return ir.BitwiseOr(ir.BitwiseNot(c), ir.BitwiseOr(a, b));
+ case 254:
+ return ir.BitwiseOr(a, ir.BitwiseOr(b, c));
+ case 255:
+ return ir.Imm32(0xFFFFFFFF);
+ // end of generated code
}
- if (ttbl & 0x02) {
- // r |= ~a & ~b & c;
- const auto lhs{ir.BitwiseAnd(not_a, not_b)};
- const auto rhs{ir.BitwiseAnd(lhs, c)};
- r = ir.BitwiseOr(r, rhs);
- }
- if (ttbl & 0x04) {
- // r |= ~a & b & ~c;
- const auto lhs{ir.BitwiseAnd(not_a, b)};
- const auto rhs{ir.BitwiseAnd(lhs, not_c)};
- r = ir.BitwiseOr(r, rhs);
- }
- if (ttbl & 0x08) {
- // r |= ~a & b & c;
- const auto lhs{ir.BitwiseAnd(not_a, b)};
- const auto rhs{ir.BitwiseAnd(lhs, c)};
- r = ir.BitwiseOr(r, rhs);
- }
- if (ttbl & 0x10) {
- // r |= a & ~b & ~c;
- const auto lhs{ir.BitwiseAnd(a, not_b)};
- const auto rhs{ir.BitwiseAnd(lhs, not_c)};
- r = ir.BitwiseOr(r, rhs);
- }
- if (ttbl & 0x20) {
- // r |= a & ~b & c;
- const auto lhs{ir.BitwiseAnd(a, not_b)};
- const auto rhs{ir.BitwiseAnd(lhs, c)};
- r = ir.BitwiseOr(r, rhs);
- }
- if (ttbl & 0x40) {
- // r |= a & b & ~c;
- const auto lhs{ir.BitwiseAnd(a, b)};
- const auto rhs{ir.BitwiseAnd(lhs, not_c)};
- r = ir.BitwiseOr(r, rhs);
- }
- if (ttbl & 0x80) {
- // r |= a & b & c;
- const auto lhs{ir.BitwiseAnd(a, b)};
- const auto rhs{ir.BitwiseAnd(lhs, c)};
- r = ir.BitwiseOr(r, rhs);
- }
- return r;
+ throw NotImplementedException("LOP3 with out of range ttbl");
}
IR::U32 LOP3(TranslatorVisitor& v, u64 insn, const IR::U32& op_b, const IR::U32& op_c, u64 lut) {
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input_lut3.py b/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input_lut3.py
new file mode 100644
index 000000000..e66d50d61
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input_lut3.py
@@ -0,0 +1,90 @@
+# SPDX-FileCopyrightText: 2022 degasus <markus@selfnet.de>
+# SPDX-License-Identifier: WTFPL
+
+from itertools import product
+
+# The primitive instructions
+OPS = {
+ 'ir.BitwiseAnd({}, {})' : (2, 1, lambda a,b: a&b),
+ 'ir.BitwiseOr({}, {})' : (2, 1, lambda a,b: a|b),
+ 'ir.BitwiseXor({}, {})' : (2, 1, lambda a,b: a^b),
+ 'ir.BitwiseNot({})' : (1, 0.1, lambda a: (~a) & 255), # Only tiny cost, as this can often inlined in other instructions
+}
+
+# Our database of combination of instructions
+optimized_calls = {}
+def cmp(lhs, rhs):
+ if lhs is None: # new entry
+ return True
+ if lhs[3] > rhs[3]: # costs
+ return True
+ if lhs[3] < rhs[3]: # costs
+ return False
+ if len(lhs[0]) > len(rhs[0]): # string len
+ return True
+ if len(lhs[0]) < len(rhs[0]): # string len
+ return False
+ if lhs[0] > rhs[0]: # string sorting
+ return True
+ if lhs[0] < rhs[0]: # string sorting
+ return False
+ assert lhs == rhs, "redundant instruction, bug in brute force"
+ return False
+def register(imm, instruction, count, latency):
+ # Use the sum of instruction count and latency as costs to evaluate which combination is best
+ costs = count + latency
+
+ old = optimized_calls.get(imm, None)
+ new = (instruction, count, latency, costs)
+
+ # Update if new or better
+ if cmp(old, new):
+ optimized_calls[imm] = new
+ return True
+
+ return False
+
+# Constants: 0, 1 (for free)
+register(0, 'ir.Imm32(0)', 0, 0)
+register(255, 'ir.Imm32(0xFFFFFFFF)', 0, 0)
+
+# Inputs: a, b, c (for free)
+ta = 0xF0
+tb = 0xCC
+tc = 0xAA
+inputs = {
+ ta : 'a',
+ tb : 'b',
+ tc : 'c',
+}
+for imm, instruction in inputs.items():
+ register(imm, instruction, 0, 0)
+ register((~imm) & 255, 'ir.BitwiseNot({})'.format(instruction), 0.099, 0.099) # slightly cheaper NEG on inputs
+
+# Try to combine two values from the db with an instruction.
+# If it is better than the old method, update it.
+while True:
+ registered = 0
+ calls_copy = optimized_calls.copy()
+ for OP, (argc, cost, f) in OPS.items():
+ for args in product(calls_copy.items(), repeat=argc):
+ # unpack(transponse) the arrays
+ imm = [arg[0] for arg in args]
+ value = [arg[1][0] for arg in args]
+ count = [arg[1][1] for arg in args]
+ latency = [arg[1][2] for arg in args]
+
+ registered += register(
+ f(*imm),
+ OP.format(*value),
+ sum(count) + cost,
+ max(latency) + cost)
+ if registered == 0:
+ # No update at all? So terminate
+ break
+
+# Hacky output. Please improve me to output valid C++ instead.
+s = """ case {imm}:
+ return {op};"""
+for imm in range(256):
+ print(s.format(imm=imm, op=optimized_calls[imm][0]))
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_predicate_to_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_predicate_to_register.cpp
index 4324fd443..37586a65d 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_predicate_to_register.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_predicate_to_register.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "shader_recompiler/exception.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp
index 6bb08db8a..82aec3b73 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp
@@ -1,11 +1,9 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
-#include "shader_recompiler/frontend/maxwell/opcodes.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
namespace Shader::Maxwell {
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_register_to_predicate.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_register_to_predicate.cpp
index eda5f177b..2369e4cf6 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_register_to_predicate.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_register_to_predicate.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "shader_recompiler/exception.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
index 20cb2674e..52be12f9c 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
index 7e26ab359..2f930f1ea 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/output_geometry.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/output_geometry.cpp
index 01cfad88d..b4b7925c9 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/output_geometry.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/output_geometry.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/pixel_load.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/pixel_load.cpp
index b4767afb5..f8607c3d7 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/pixel_load.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/pixel_load.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/predicate_set_predicate.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/predicate_set_predicate.cpp
index 75d1fa8c1..3b7d53953 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/predicate_set_predicate.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/predicate_set_predicate.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/predicate_set_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/predicate_set_register.cpp
index b02789874..cd1179c7d 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/predicate_set_register.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/predicate_set_register.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/select_source_with_predicate.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/select_source_with_predicate.cpp
index 93baa75a9..992d6d1af 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/select_source_with_predicate.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/select_source_with_predicate.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/surface_atomic_operations.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/surface_atomic_operations.cpp
index 63b588ad4..8ec90f52e 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/surface_atomic_operations.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/surface_atomic_operations.cpp
@@ -1,9 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <array>
-#include <bit>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/surface_load_store.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/surface_load_store.cpp
index 681220a8d..c01ab361b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/surface_load_store.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/surface_load_store.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <bit>
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp
index 0046b5edd..2459fc30d 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <optional>
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch_swizzled.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch_swizzled.cpp
index 154e7f1a1..50e618c2f 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch_swizzled.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch_swizzled.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <utility>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gather.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gather.cpp
index 218cbc1a8..3263b3c7c 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gather.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gather.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <optional>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gather_swizzled.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gather_swizzled.cpp
index 34efa2d50..e4c658145 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gather_swizzled.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gather_swizzled.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <utility>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gradient.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gradient.cpp
index c3fe3ffda..dd34507bc 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gradient.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gradient.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <optional>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
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 983058303..0a7821e18 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_load.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_load.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <optional>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
@@ -134,7 +131,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_load_swizzled.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_load_swizzled.cpp
index 5dd7e31b2..0362f076a 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_load_swizzled.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_load_swizzled.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
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 aea3c0e62..639da1e9c 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
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <optional>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
@@ -83,7 +80,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/frontend/maxwell/translate/impl/texture_query.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_query.cpp
index 0459e5473..f8cfd4ab6 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_query.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_query.cpp
@@ -1,12 +1,10 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <optional>
#include "common/bit_field.h"
#include "common/common_types.h"
-#include "shader_recompiler/frontend/ir/modifiers.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
namespace Shader::Maxwell {
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/video_helper.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/video_helper.cpp
index e1f4174cf..7d7444aff 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/video_helper.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/video_helper.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/exception.h"
#include "shader_recompiler/frontend/maxwell/translate/impl/video_helper.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/video_helper.h b/src/shader_recompiler/frontend/maxwell/translate/impl/video_helper.h
index 40c0b907c..cbf253b6f 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/video_helper.h
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/video_helper.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/video_minimum_maximum.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/video_minimum_maximum.cpp
index 4851b0b8d..5ac25beee 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/video_minimum_maximum.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/video_minimum_maximum.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/video_multiply_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/video_multiply_add.cpp
index cc2e6d6e6..9272a6d03 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/video_multiply_add.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/video_multiply_add.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/video_set_predicate.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/video_set_predicate.cpp
index 1b66abc33..73db35304 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/video_set_predicate.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/video_set_predicate.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
#include "shader_recompiler/exception.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/vote.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/vote.cpp
index 7ce370f09..70e490e80 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/vote.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/vote.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/warp_shuffle.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/warp_shuffle.cpp
index 550fed55c..f0436994b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/warp_shuffle.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/warp_shuffle.cpp
@@ -1,8 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <optional>
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "common/common_types.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/translate.cpp b/src/shader_recompiler/frontend/maxwell/translate/translate.cpp
index 8e3c4c5d5..6d6844826 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/translate.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/translate.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/environment.h"
#include "shader_recompiler/frontend/ir/basic_block.h"
diff --git a/src/shader_recompiler/frontend/maxwell/translate/translate.h b/src/shader_recompiler/frontend/maxwell/translate/translate.h
index a3edd2e46..2f98f0ec1 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/translate.h
+++ b/src/shader_recompiler/frontend/maxwell/translate/translate.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
index 248ad3ced..77efb4f57 100644
--- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <memory>
@@ -212,11 +211,11 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
}
Optimization::SsaRewritePass(program);
+ Optimization::ConstantPropagationPass(program);
+
Optimization::GlobalMemoryToStorageBufferPass(program);
Optimization::TexturePass(env, program);
- Optimization::ConstantPropagationPass(program);
-
if (Settings::values.resolution_info.active) {
Optimization::RescalingPass(program);
}
diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.h b/src/shader_recompiler/frontend/maxwell/translate_program.h
index eac83da9d..02ede8c9c 100644
--- a/src/shader_recompiler/frontend/maxwell/translate_program.h
+++ b/src/shader_recompiler/frontend/maxwell/translate_program.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,10 +7,13 @@
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/program.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"
-#include "shader_recompiler/host_translate_info.h"
#include "shader_recompiler/object_pool.h"
#include "shader_recompiler/runtime_info.h"
+namespace Shader {
+struct HostTranslateInfo;
+}
+
namespace Shader::Maxwell {
[[nodiscard]] IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool,
diff --git a/src/shader_recompiler/host_translate_info.h b/src/shader_recompiler/host_translate_info.h
index 96468b2e7..881874310 100644
--- a/src/shader_recompiler/host_translate_info.h
+++ b/src/shader_recompiler/host_translate_info.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
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 bfd2ae650..7cff8ecdc 100644
--- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
+++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
#include "shader_recompiler/environment.h"
@@ -29,6 +28,41 @@ void AddConstantBufferDescriptor(Info& info, u32 index, u32 count) {
});
}
+void AddRegisterIndexedLdc(Info& info) {
+ info.uses_cbuf_indirect = true;
+
+ 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;
+ }
+}
+
+u32 GetElementSize(IR::Type& used_type, Shader::IR::Opcode opcode) {
+ switch (opcode) {
+ case IR::Opcode::GetCbufU8:
+ case IR::Opcode::GetCbufS8:
+ used_type |= IR::Type::U8;
+ return 1;
+ case IR::Opcode::GetCbufU16:
+ case IR::Opcode::GetCbufS16:
+ used_type |= IR::Type::U16;
+ return 2;
+ case IR::Opcode::GetCbufU32:
+ used_type |= IR::Type::U32;
+ return 4;
+ case IR::Opcode::GetCbufF32:
+ used_type |= IR::Type::F32;
+ return 4;
+ case IR::Opcode::GetCbufU32x2:
+ used_type |= IR::Type::U32x2;
+ return 8;
+ default:
+ throw InvalidArgument("Invalid opcode {}", opcode);
+ }
+}
+
void GetPatch(Info& info, IR::Patch patch) {
if (!IR::IsGeneric(patch)) {
throw NotImplementedException("Reading non-generic patch {}", patch);
@@ -463,42 +497,18 @@ void VisitUsages(Info& info, IR::Inst& inst) {
case IR::Opcode::GetCbufU32x2: {
const IR::Value index{inst.Arg(0)};
const IR::Value offset{inst.Arg(1)};
- if (!index.IsImmediate()) {
- throw NotImplementedException("Constant buffer with non-immediate index");
- }
- AddConstantBufferDescriptor(info, index.U32(), 1);
- u32 element_size{};
- switch (inst.GetOpcode()) {
- case IR::Opcode::GetCbufU8:
- case IR::Opcode::GetCbufS8:
- info.used_constant_buffer_types |= IR::Type::U8;
- element_size = 1;
- break;
- case IR::Opcode::GetCbufU16:
- case IR::Opcode::GetCbufS16:
- info.used_constant_buffer_types |= IR::Type::U16;
- element_size = 2;
- break;
- case IR::Opcode::GetCbufU32:
- info.used_constant_buffer_types |= IR::Type::U32;
- element_size = 4;
- break;
- case IR::Opcode::GetCbufF32:
- info.used_constant_buffer_types |= IR::Type::F32;
- element_size = 4;
- break;
- case IR::Opcode::GetCbufU32x2:
- info.used_constant_buffer_types |= IR::Type::U32x2;
- element_size = 8;
- break;
- default:
- break;
- }
- u32& size{info.constant_buffer_used_sizes[index.U32()]};
- if (offset.IsImmediate()) {
- size = Common::AlignUp(std::max(size, offset.U32() + element_size), 16u);
+ if (index.IsImmediate()) {
+ AddConstantBufferDescriptor(info, index.U32(), 1);
+ u32 element_size = GetElementSize(info.used_constant_buffer_types, inst.GetOpcode());
+ u32& size{info.constant_buffer_used_sizes[index.U32()]};
+ if (offset.IsImmediate()) {
+ size = Common::AlignUp(std::max(size, offset.U32() + element_size), 16u);
+ } else {
+ size = 0x10'000;
+ }
} else {
- size = 0x10'000;
+ AddRegisterIndexedLdc(info);
+ GetElementSize(info.used_indirect_cbuf_types, inst.GetOpcode());
}
break;
}
diff --git a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp
index c134a12bc..826f9a54a 100644
--- a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp
+++ b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <functional>
@@ -8,7 +7,6 @@
#include <type_traits>
#include "common/bit_cast.h"
-#include "common/bit_util.h"
#include "shader_recompiler/exception.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
#include "shader_recompiler/frontend/ir/value.h"
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..9a7d47344 100644
--- a/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp
+++ b/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp
@@ -1,25 +1,104 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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/ir_opt/dual_vertex_pass.cpp b/src/shader_recompiler/ir_opt/dual_vertex_pass.cpp
index 055ba9c54..900f6f9fd 100644
--- a/src/shader_recompiler/ir_opt/dual_vertex_pass.cpp
+++ b/src/shader_recompiler/ir_opt/dual_vertex_pass.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/frontend/ir/ir_emitter.h"
#include "shader_recompiler/ir_opt/passes.h"
diff --git a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
index 38592afd0..336338e62 100644
--- a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
+++ b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
@@ -1,11 +1,7 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include <algorithm>
-#include <compare>
#include <optional>
-#include <queue>
#include <boost/container/flat_set.hpp>
#include <boost/container/small_vector.hpp>
@@ -334,7 +330,8 @@ std::optional<LowAddrInfo> TrackLowAddress(IR::Inst* inst) {
/// Tries to track the storage buffer address used by a global memory instruction
std::optional<StorageBufferAddr> Track(const IR::Value& value, const Bias* bias) {
const auto pred{[bias](const IR::Inst* inst) -> std::optional<StorageBufferAddr> {
- if (inst->GetOpcode() != IR::Opcode::GetCbufU32) {
+ if (inst->GetOpcode() != IR::Opcode::GetCbufU32 &&
+ inst->GetOpcode() != IR::Opcode::GetCbufU32x2) {
return std::nullopt;
}
const IR::Value index{inst->Arg(0)};
diff --git a/src/shader_recompiler/ir_opt/identity_removal_pass.cpp b/src/shader_recompiler/ir_opt/identity_removal_pass.cpp
index e9b55f835..951534bbf 100644
--- a/src/shader_recompiler/ir_opt/identity_removal_pass.cpp
+++ b/src/shader_recompiler/ir_opt/identity_removal_pass.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <vector>
diff --git a/src/shader_recompiler/ir_opt/lower_fp16_to_fp32.cpp b/src/shader_recompiler/ir_opt/lower_fp16_to_fp32.cpp
index 773e1f961..71e12b3e4 100644
--- a/src/shader_recompiler/ir_opt/lower_fp16_to_fp32.cpp
+++ b/src/shader_recompiler/ir_opt/lower_fp16_to_fp32.cpp
@@ -1,10 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include <algorithm>
-
-#include "shader_recompiler/frontend/ir/ir_emitter.h"
#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/ir_opt/passes.h"
diff --git a/src/shader_recompiler/ir_opt/lower_int64_to_int32.cpp b/src/shader_recompiler/ir_opt/lower_int64_to_int32.cpp
index c2654cd9b..cdb58f46b 100644
--- a/src/shader_recompiler/ir_opt/lower_int64_to_int32.cpp
+++ b/src/shader_recompiler/ir_opt/lower_int64_to_int32.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <utility>
diff --git a/src/shader_recompiler/ir_opt/passes.h b/src/shader_recompiler/ir_opt/passes.h
index f877c7ba0..6ff8e4266 100644
--- a/src/shader_recompiler/ir_opt/passes.h
+++ b/src/shader_recompiler/ir_opt/passes.h
@@ -1,13 +1,9 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <span>
-
#include "shader_recompiler/environment.h"
-#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/program.h"
namespace Shader::Optimization {
diff --git a/src/shader_recompiler/ir_opt/rescaling_pass.cpp b/src/shader_recompiler/ir_opt/rescaling_pass.cpp
index c28500dd1..9198fa5f2 100644
--- a/src/shader_recompiler/ir_opt/rescaling_pass.cpp
+++ b/src/shader_recompiler/ir_opt/rescaling_pass.cpp
@@ -1,8 +1,6 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include "common/alignment.h"
#include "common/settings.h"
#include "shader_recompiler/environment.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
@@ -18,6 +16,7 @@ namespace {
switch (type) {
case TextureType::Color2D:
case TextureType::ColorArray2D:
+ case TextureType::Color2DRect:
return true;
case TextureType::Color1D:
case TextureType::ColorArray1D:
@@ -134,7 +133,8 @@ void PatchImageQueryDimensions(IR::Block& block, IR::Inst& inst) {
const IR::U1 is_scaled{ir.IsTextureScaled(ir.Imm32(info.descriptor_index))};
switch (info.type) {
case TextureType::Color2D:
- case TextureType::ColorArray2D: {
+ case TextureType::ColorArray2D:
+ case TextureType::Color2DRect: {
const IR::Value new_inst{&*block.PrependNewInst(it, inst)};
const IR::U32 width{DownScale(ir, is_scaled, IR::U32{ir.CompositeExtract(new_inst, 0)})};
const IR::U32 height{DownScale(ir, is_scaled, IR::U32{ir.CompositeExtract(new_inst, 1)})};
@@ -165,6 +165,7 @@ void ScaleIntegerComposite(IR::IREmitter& ir, IR::Inst& inst, const IR::U1& is_s
const IR::U32 y{Scale(ir, is_scaled, IR::U32{ir.CompositeExtract(composite, 1)})};
switch (info.type) {
case TextureType::Color2D:
+ case TextureType::Color2DRect:
inst.SetArg(index, ir.CompositeConstruct(x, y));
break;
case TextureType::ColorArray2D: {
@@ -183,6 +184,32 @@ void ScaleIntegerComposite(IR::IREmitter& ir, IR::Inst& inst, const IR::U1& is_s
}
}
+void ScaleIntegerOffsetComposite(IR::IREmitter& ir, IR::Inst& inst, const IR::U1& is_scaled,
+ size_t index) {
+ const IR::Value composite{inst.Arg(index)};
+ if (composite.IsEmpty()) {
+ return;
+ }
+ const auto info{inst.Flags<IR::TextureInstInfo>()};
+ const IR::U32 x{Scale(ir, is_scaled, IR::U32{ir.CompositeExtract(composite, 0)})};
+ const IR::U32 y{Scale(ir, is_scaled, IR::U32{ir.CompositeExtract(composite, 1)})};
+ switch (info.type) {
+ case TextureType::ColorArray2D:
+ case TextureType::Color2D:
+ case TextureType::Color2DRect:
+ inst.SetArg(index, ir.CompositeConstruct(x, y));
+ break;
+ case TextureType::Color1D:
+ case TextureType::ColorArray1D:
+ case TextureType::Color3D:
+ case TextureType::ColorCube:
+ case TextureType::ColorArrayCube:
+ case TextureType::Buffer:
+ // Nothing to patch here
+ break;
+ }
+}
+
void SubScaleCoord(IR::IREmitter& ir, IR::Inst& inst, const IR::U1& is_scaled) {
const auto info{inst.Flags<IR::TextureInstInfo>()};
const IR::Value coord{inst.Arg(1)};
@@ -193,6 +220,7 @@ void SubScaleCoord(IR::IREmitter& ir, IR::Inst& inst, const IR::U1& is_scaled) {
const IR::U32 scaled_y{SubScale(ir, is_scaled, coord_y, IR::Attribute::PositionY)};
switch (info.type) {
case TextureType::Color2D:
+ case TextureType::Color2DRect:
inst.SetArg(1, ir.CompositeConstruct(scaled_x, scaled_y));
break;
case TextureType::ColorArray2D: {
@@ -220,7 +248,7 @@ void SubScaleImageFetch(IR::Block& block, IR::Inst& inst) {
const IR::U1 is_scaled{ir.IsTextureScaled(ir.Imm32(info.descriptor_index))};
SubScaleCoord(ir, inst, is_scaled);
// Scale ImageFetch offset
- ScaleIntegerComposite(ir, inst, is_scaled, 2);
+ ScaleIntegerOffsetComposite(ir, inst, is_scaled, 2);
}
void SubScaleImageRead(IR::Block& block, IR::Inst& inst) {
@@ -242,7 +270,7 @@ void PatchImageFetch(IR::Block& block, IR::Inst& inst) {
const IR::U1 is_scaled{ir.IsTextureScaled(ir.Imm32(info.descriptor_index))};
ScaleIntegerComposite(ir, inst, is_scaled, 1);
// Scale ImageFetch offset
- ScaleIntegerComposite(ir, inst, is_scaled, 2);
+ ScaleIntegerOffsetComposite(ir, inst, is_scaled, 2);
}
void PatchImageRead(IR::Block& block, IR::Inst& inst) {
diff --git a/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp b/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp
index 87aa09358..d0b145860 100644
--- a/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp
+++ b/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
// This file implements the SSA rewriting algorithm proposed in
//
@@ -20,7 +19,6 @@
#include <vector>
#include <boost/container/flat_map.hpp>
-#include <boost/container/flat_set.hpp>
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/opcodes.h"
diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp
index 96c997a58..e8be58357 100644
--- a/src/shader_recompiler/ir_opt/texture_pass.cpp
+++ b/src/shader_recompiler/ir_opt/texture_pass.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <bit>
@@ -20,8 +19,10 @@ namespace {
struct ConstBufferAddr {
u32 index;
u32 offset;
+ u32 shift_left;
u32 secondary_index;
u32 secondary_offset;
+ u32 secondary_shift_left;
IR::U32 dynamic_offset;
u32 count;
bool has_secondary;
@@ -173,19 +174,41 @@ bool IsTextureInstruction(const IR::Inst& inst) {
return IndexedInstruction(inst) != IR::Opcode::Void;
}
-std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst);
+std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env);
-std::optional<ConstBufferAddr> Track(const IR::Value& value) {
- return IR::BreadthFirstSearch(value, TryGetConstBuffer);
+std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env) {
+ return IR::BreadthFirstSearch(
+ value, [&env](const IR::Inst* inst) { return TryGetConstBuffer(inst, env); });
}
-std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
+std::optional<u32> TryGetConstant(IR::Value& value, Environment& env) {
+ const IR::Inst* inst = value.InstRecursive();
+ if (inst->GetOpcode() != IR::Opcode::GetCbufU32) {
+ return std::nullopt;
+ }
+ const IR::Value index{inst->Arg(0)};
+ const IR::Value offset{inst->Arg(1)};
+ if (!index.IsImmediate()) {
+ return std::nullopt;
+ }
+ if (!offset.IsImmediate()) {
+ return std::nullopt;
+ }
+ const auto index_number = index.U32();
+ if (index_number != 1) {
+ return std::nullopt;
+ }
+ const auto offset_number = offset.U32();
+ return env.ReadCbufValue(index_number, offset_number);
+}
+
+std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env) {
switch (inst->GetOpcode()) {
default:
return std::nullopt;
case IR::Opcode::BitwiseOr32: {
- std::optional lhs{Track(inst->Arg(0))};
- std::optional rhs{Track(inst->Arg(1))};
+ std::optional lhs{Track(inst->Arg(0), env)};
+ std::optional rhs{Track(inst->Arg(1), env)};
if (!lhs || !rhs) {
return std::nullopt;
}
@@ -195,19 +218,62 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
if (lhs->count > 1 || rhs->count > 1) {
return std::nullopt;
}
- if (lhs->index > rhs->index || lhs->offset > rhs->offset) {
+ if (lhs->shift_left > 0 || lhs->index > rhs->index || lhs->offset > rhs->offset) {
std::swap(lhs, rhs);
}
return ConstBufferAddr{
.index = lhs->index,
.offset = lhs->offset,
+ .shift_left = lhs->shift_left,
.secondary_index = rhs->index,
.secondary_offset = rhs->offset,
+ .secondary_shift_left = rhs->shift_left,
.dynamic_offset = {},
.count = 1,
.has_secondary = true,
};
}
+ case IR::Opcode::ShiftLeftLogical32: {
+ const IR::Value shift{inst->Arg(1)};
+ if (!shift.IsImmediate()) {
+ return std::nullopt;
+ }
+ std::optional lhs{Track(inst->Arg(0), env)};
+ if (lhs) {
+ lhs->shift_left = shift.U32();
+ }
+ return lhs;
+ break;
+ }
+ case IR::Opcode::BitwiseAnd32: {
+ IR::Value op1{inst->Arg(0)};
+ IR::Value op2{inst->Arg(1)};
+ if (op1.IsImmediate()) {
+ std::swap(op1, op2);
+ }
+ if (!op2.IsImmediate() && !op1.IsImmediate()) {
+ do {
+ auto try_index = TryGetConstant(op1, env);
+ if (try_index) {
+ op1 = op2;
+ op2 = IR::Value{*try_index};
+ break;
+ }
+ auto try_index_2 = TryGetConstant(op2, env);
+ if (try_index_2) {
+ op2 = IR::Value{*try_index_2};
+ break;
+ }
+ return std::nullopt;
+ } while (false);
+ }
+ std::optional lhs{Track(op1, env)};
+ if (lhs) {
+ lhs->shift_left = static_cast<u32>(std::countr_zero(op2.U32()));
+ }
+ return lhs;
+ break;
+ }
case IR::Opcode::GetCbufU32x2:
case IR::Opcode::GetCbufU32:
break;
@@ -223,8 +289,10 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
return ConstBufferAddr{
.index = index.U32(),
.offset = offset.U32(),
+ .shift_left = 0,
.secondary_index = 0,
.secondary_offset = 0,
+ .secondary_shift_left = 0,
.dynamic_offset = {},
.count = 1,
.has_secondary = false,
@@ -248,8 +316,10 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
return ConstBufferAddr{
.index = index.U32(),
.offset = base_offset,
+ .shift_left = 0,
.secondary_index = 0,
.secondary_offset = 0,
+ .secondary_shift_left = 0,
.dynamic_offset = dynamic_offset,
.count = 8,
.has_secondary = false,
@@ -259,7 +329,7 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) {
ConstBufferAddr addr;
if (IsBindless(inst)) {
- const std::optional<ConstBufferAddr> track_addr{Track(inst.Arg(0))};
+ const std::optional<ConstBufferAddr> track_addr{Track(inst.Arg(0), env)};
if (!track_addr) {
throw NotImplementedException("Failed to track bindless texture constant buffer");
}
@@ -268,8 +338,10 @@ TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) {
addr = ConstBufferAddr{
.index = env.TextureBoundBuffer(),
.offset = inst.Arg(0).U32(),
+ .shift_left = 0,
.secondary_index = 0,
.secondary_offset = 0,
+ .secondary_shift_left = 0,
.dynamic_offset = {},
.count = 1,
.has_secondary = false,
@@ -285,8 +357,9 @@ TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) {
TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
const u32 secondary_index{cbuf.has_secondary ? cbuf.secondary_index : cbuf.index};
const u32 secondary_offset{cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset};
- const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset)};
- const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)};
+ const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset) << cbuf.shift_left};
+ const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)
+ << cbuf.secondary_shift_left};
return env.ReadTextureType(lhs_raw | rhs_raw);
}
@@ -363,6 +436,21 @@ private:
TextureDescriptors& texture_descriptors;
ImageDescriptors& image_descriptors;
};
+
+void PatchImageSampleImplicitLod(IR::Block& block, IR::Inst& inst) {
+ IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
+ const auto info{inst.Flags<IR::TextureInstInfo>()};
+ const IR::Value coord(inst.Arg(1));
+ const IR::Value handle(ir.Imm32(0));
+ const IR::U32 lod{ir.Imm32(0)};
+ const IR::Value texture_size = ir.ImageQueryDimension(handle, lod, info);
+ inst.SetArg(
+ 1, ir.CompositeConstruct(
+ ir.FPMul(IR::F32(ir.CompositeExtract(coord, 0)),
+ ir.FPRecip(ir.ConvertUToF(32, 32, ir.CompositeExtract(texture_size, 0)))),
+ ir.FPMul(IR::F32(ir.CompositeExtract(coord, 1)),
+ ir.FPRecip(ir.ConvertUToF(32, 32, ir.CompositeExtract(texture_size, 1))))));
+}
} // Anonymous namespace
void TexturePass(Environment& env, IR::Program& program) {
@@ -400,6 +488,14 @@ void TexturePass(Environment& env, IR::Program& program) {
flags.type.Assign(ReadTextureType(env, cbuf));
inst->SetFlags(flags);
break;
+ case IR::Opcode::ImageSampleImplicitLod:
+ if (flags.type != TextureType::Color2D) {
+ break;
+ }
+ if (ReadTextureType(env, cbuf) == TextureType::Color2DRect) {
+ PatchImageSampleImplicitLod(*texture_inst.block, *texture_inst.inst);
+ }
+ break;
case IR::Opcode::ImageFetch:
if (flags.type != TextureType::Color1D) {
break;
@@ -465,8 +561,10 @@ void TexturePass(Environment& env, IR::Program& program) {
.has_secondary = cbuf.has_secondary,
.cbuf_index = cbuf.index,
.cbuf_offset = cbuf.offset,
+ .shift_left = cbuf.shift_left,
.secondary_cbuf_index = cbuf.secondary_index,
.secondary_cbuf_offset = cbuf.secondary_offset,
+ .secondary_shift_left = cbuf.secondary_shift_left,
.count = cbuf.count,
.size_shift = DESCRIPTOR_SIZE_SHIFT,
});
@@ -477,8 +575,10 @@ void TexturePass(Environment& env, IR::Program& program) {
.has_secondary = cbuf.has_secondary,
.cbuf_index = cbuf.index,
.cbuf_offset = cbuf.offset,
+ .shift_left = cbuf.shift_left,
.secondary_cbuf_index = cbuf.secondary_index,
.secondary_cbuf_offset = cbuf.secondary_offset,
+ .secondary_shift_left = cbuf.secondary_shift_left,
.count = cbuf.count,
.size_shift = DESCRIPTOR_SIZE_SHIFT,
});
diff --git a/src/shader_recompiler/ir_opt/verification_pass.cpp b/src/shader_recompiler/ir_opt/verification_pass.cpp
index 975d5aadf..f89f4ac28 100644
--- a/src/shader_recompiler/ir_opt/verification_pass.cpp
+++ b/src/shader_recompiler/ir_opt/verification_pass.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <map>
#include <set>
@@ -44,7 +43,7 @@ static void ValidateUses(const IR::Program& program) {
}
}
}
- for (const auto [inst, uses] : actual_uses) {
+ for (const auto& [inst, uses] : actual_uses) {
if (inst->UseCount() != uses) {
throw LogicError("Invalid uses in block: {}", IR::DumpProgram(program));
}
diff --git a/src/shader_recompiler/object_pool.h b/src/shader_recompiler/object_pool.h
index a12ddcc8f..2b42c4ba2 100644
--- a/src/shader_recompiler/object_pool.h
+++ b/src/shader_recompiler/object_pool.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h
index dc4c806ff..21d3d236b 100644
--- a/src/shader_recompiler/profile.h
+++ b/src/shader_recompiler/profile.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/program_header.h b/src/shader_recompiler/program_header.h
index bd6c2bfb5..1e8bae7cd 100644
--- a/src/shader_recompiler/program_header.h
+++ b/src/shader_recompiler/program_header.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -196,6 +195,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/runtime_info.h b/src/shader_recompiler/runtime_info.h
index f3f83a258..dcb5ab158 100644
--- a/src/shader_recompiler/runtime_info.h
+++ b/src/shader_recompiler/runtime_info.h
@@ -1,11 +1,9 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
-#include <bitset>
#include <optional>
#include <vector>
diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h
index 9f375c30e..cc596da4f 100644
--- a/src/shader_recompiler/shader_info.h
+++ b/src/shader_recompiler/shader_info.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -25,8 +24,9 @@ enum class TextureType : u32 {
ColorCube,
ColorArrayCube,
Buffer,
+ Color2DRect,
};
-constexpr u32 NUM_TEXTURE_TYPES = 8;
+constexpr u32 NUM_TEXTURE_TYPES = 9;
enum class ImageFormat : u32 {
Typeless,
@@ -61,8 +61,10 @@ struct TextureBufferDescriptor {
bool has_secondary;
u32 cbuf_index;
u32 cbuf_offset;
+ u32 shift_left;
u32 secondary_cbuf_index;
u32 secondary_cbuf_offset;
+ u32 secondary_shift_left;
u32 count;
u32 size_shift;
};
@@ -85,8 +87,10 @@ struct TextureDescriptor {
bool has_secondary;
u32 cbuf_index;
u32 cbuf_offset;
+ u32 shift_left;
u32 secondary_cbuf_index;
u32 secondary_cbuf_offset;
+ u32 secondary_shift_left;
u32 count;
u32 size_shift;
};
@@ -105,6 +109,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};
@@ -173,9 +178,11 @@ struct Info {
bool uses_atomic_image_u32{};
bool uses_shadow_lod{};
bool uses_rescaling_uniform{};
+ bool uses_cbuf_indirect{};
IR::Type used_constant_buffer_types{};
IR::Type used_storage_buffer_types{};
+ IR::Type used_indirect_cbuf_types{};
u32 constant_buffer_mask{};
std::array<u32, MAX_CBUFS> constant_buffer_used_sizes{};
diff --git a/src/shader_recompiler/stage.h b/src/shader_recompiler/stage.h
index 5c1c8d8fc..9af0a1b72 100644
--- a/src/shader_recompiler/stage.h
+++ b/src/shader_recompiler/stage.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/shader_recompiler/varying_state.h b/src/shader_recompiler/varying_state.h
index bc4f273c8..7b28a285f 100644
--- a/src/shader_recompiler/varying_state.h
+++ b/src/shader_recompiler/varying_state.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index a69ccb264..43ad2c7ff 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
add_executable(tests
common/bit_field.cpp
common/cityhash.cpp
@@ -7,7 +10,7 @@ add_executable(tests
common/ring_buffer.cpp
common/unique_function.cpp
core/core_timing.cpp
- core/network/network.cpp
+ core/internal_network/network.cpp
tests.cpp
video_core/buffer_base.cpp
input_common/calibration_configuration_job.cpp
diff --git a/src/tests/common/bit_field.cpp b/src/tests/common/bit_field.cpp
index 182638000..0071ae52e 100644
--- a/src/tests/common/bit_field.cpp
+++ b/src/tests/common/bit_field.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2019 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <cstring>
diff --git a/src/tests/common/cityhash.cpp b/src/tests/common/cityhash.cpp
index 7a40b6c4a..05942eadb 100644
--- a/src/tests/common/cityhash.cpp
+++ b/src/tests/common/cityhash.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <catch2/catch.hpp>
diff --git a/src/tests/common/fibers.cpp b/src/tests/common/fibers.cpp
index 751cbe196..4e29f9199 100644
--- a/src/tests/common/fibers.cpp
+++ b/src/tests/common/fibers.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
#include <cstdlib>
@@ -44,7 +43,15 @@ class TestControl1 {
public:
TestControl1() = default;
- void DoWork();
+ void DoWork() {
+ const u32 id = thread_ids.Get();
+ u32 value = items[id];
+ for (u32 i = 0; i < id; i++) {
+ value++;
+ }
+ results[id] = value;
+ Fiber::YieldTo(work_fibers[id], *thread_fibers[id]);
+ }
void ExecuteThread(u32 id);
@@ -55,35 +62,16 @@ public:
std::vector<u32> results;
};
-static void WorkControl1(void* control) {
- auto* test_control = static_cast<TestControl1*>(control);
- test_control->DoWork();
-}
-
-void TestControl1::DoWork() {
- const u32 id = thread_ids.Get();
- u32 value = items[id];
- for (u32 i = 0; i < id; i++) {
- value++;
- }
- results[id] = value;
- Fiber::YieldTo(work_fibers[id], *thread_fibers[id]);
-}
-
void TestControl1::ExecuteThread(u32 id) {
thread_ids.Register(id);
auto thread_fiber = Fiber::ThreadToFiber();
thread_fibers[id] = thread_fiber;
- work_fibers[id] = std::make_shared<Fiber>(std::function<void(void*)>{WorkControl1}, this);
+ work_fibers[id] = std::make_shared<Fiber>([this] { DoWork(); });
items[id] = rand() % 256;
Fiber::YieldTo(thread_fibers[id], *work_fibers[id]);
thread_fibers[id]->Exit();
}
-static void ThreadStart1(u32 id, TestControl1& test_control) {
- test_control.ExecuteThread(id);
-}
-
/** This test checks for fiber setup configuration and validates that fibers are
* doing all the work required.
*/
@@ -96,7 +84,7 @@ TEST_CASE("Fibers::Setup", "[common]") {
test_control.results.resize(num_threads, 0);
std::vector<std::thread> threads;
for (u32 i = 0; i < num_threads; i++) {
- threads.emplace_back(ThreadStart1, i, std::ref(test_control));
+ threads.emplace_back([&test_control, i] { test_control.ExecuteThread(i); });
}
for (u32 i = 0; i < num_threads; i++) {
threads[i].join();
@@ -168,21 +156,6 @@ public:
std::shared_ptr<Common::Fiber> fiber3;
};
-static void WorkControl2_1(void* control) {
- auto* test_control = static_cast<TestControl2*>(control);
- test_control->DoWork1();
-}
-
-static void WorkControl2_2(void* control) {
- auto* test_control = static_cast<TestControl2*>(control);
- test_control->DoWork2();
-}
-
-static void WorkControl2_3(void* control) {
- auto* test_control = static_cast<TestControl2*>(control);
- test_control->DoWork3();
-}
-
void TestControl2::ExecuteThread(u32 id) {
thread_ids.Register(id);
auto thread_fiber = Fiber::ThreadToFiber();
@@ -194,18 +167,6 @@ void TestControl2::Exit() {
thread_fibers[id]->Exit();
}
-static void ThreadStart2_1(u32 id, TestControl2& test_control) {
- test_control.ExecuteThread(id);
- test_control.CallFiber1();
- test_control.Exit();
-}
-
-static void ThreadStart2_2(u32 id, TestControl2& test_control) {
- test_control.ExecuteThread(id);
- test_control.CallFiber2();
- test_control.Exit();
-}
-
/** This test checks for fiber thread exchange configuration and validates that fibers are
* that a fiber has been successfully transferred from one thread to another and that the TLS
* region of the thread is kept while changing fibers.
@@ -213,14 +174,19 @@ static void ThreadStart2_2(u32 id, TestControl2& test_control) {
TEST_CASE("Fibers::InterExchange", "[common]") {
TestControl2 test_control{};
test_control.thread_fibers.resize(2);
- test_control.fiber1 =
- std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_1}, &test_control);
- test_control.fiber2 =
- std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_2}, &test_control);
- test_control.fiber3 =
- std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_3}, &test_control);
- std::thread thread1(ThreadStart2_1, 0, std::ref(test_control));
- std::thread thread2(ThreadStart2_2, 1, std::ref(test_control));
+ test_control.fiber1 = std::make_shared<Fiber>([&test_control] { test_control.DoWork1(); });
+ test_control.fiber2 = std::make_shared<Fiber>([&test_control] { test_control.DoWork2(); });
+ test_control.fiber3 = std::make_shared<Fiber>([&test_control] { test_control.DoWork3(); });
+ std::thread thread1{[&test_control] {
+ test_control.ExecuteThread(0);
+ test_control.CallFiber1();
+ test_control.Exit();
+ }};
+ std::thread thread2{[&test_control] {
+ test_control.ExecuteThread(1);
+ test_control.CallFiber2();
+ test_control.Exit();
+ }};
thread1.join();
thread2.join();
REQUIRE(test_control.assert1);
@@ -271,16 +237,6 @@ public:
std::shared_ptr<Common::Fiber> fiber2;
};
-static void WorkControl3_1(void* control) {
- auto* test_control = static_cast<TestControl3*>(control);
- test_control->DoWork1();
-}
-
-static void WorkControl3_2(void* control) {
- auto* test_control = static_cast<TestControl3*>(control);
- test_control->DoWork2();
-}
-
void TestControl3::ExecuteThread(u32 id) {
thread_ids.Register(id);
auto thread_fiber = Fiber::ThreadToFiber();
@@ -292,12 +248,6 @@ void TestControl3::Exit() {
thread_fibers[id]->Exit();
}
-static void ThreadStart3(u32 id, TestControl3& test_control) {
- test_control.ExecuteThread(id);
- test_control.CallFiber1();
- test_control.Exit();
-}
-
/** This test checks for one two threads racing for starting the same fiber.
* It checks execution occurred in an ordered manner and by no time there were
* two contexts at the same time.
@@ -305,12 +255,15 @@ static void ThreadStart3(u32 id, TestControl3& test_control) {
TEST_CASE("Fibers::StartRace", "[common]") {
TestControl3 test_control{};
test_control.thread_fibers.resize(2);
- test_control.fiber1 =
- std::make_shared<Fiber>(std::function<void(void*)>{WorkControl3_1}, &test_control);
- test_control.fiber2 =
- std::make_shared<Fiber>(std::function<void(void*)>{WorkControl3_2}, &test_control);
- std::thread thread1(ThreadStart3, 0, std::ref(test_control));
- std::thread thread2(ThreadStart3, 1, std::ref(test_control));
+ test_control.fiber1 = std::make_shared<Fiber>([&test_control] { test_control.DoWork1(); });
+ test_control.fiber2 = std::make_shared<Fiber>([&test_control] { test_control.DoWork2(); });
+ const auto race_function{[&test_control](u32 id) {
+ test_control.ExecuteThread(id);
+ test_control.CallFiber1();
+ test_control.Exit();
+ }};
+ std::thread thread1([&] { race_function(0); });
+ std::thread thread2([&] { race_function(1); });
thread1.join();
thread2.join();
REQUIRE(test_control.value1 == 1);
@@ -320,12 +273,10 @@ TEST_CASE("Fibers::StartRace", "[common]") {
class TestControl4;
-static void WorkControl4(void* control);
-
class TestControl4 {
public:
TestControl4() {
- fiber1 = std::make_shared<Fiber>(std::function<void(void*)>{WorkControl4}, this);
+ fiber1 = std::make_shared<Fiber>([this] { DoWork(); });
goal_reached = false;
rewinded = false;
}
@@ -337,7 +288,7 @@ public:
}
void DoWork() {
- fiber1->SetRewindPoint(std::function<void(void*)>{WorkControl4}, this);
+ fiber1->SetRewindPoint([this] { DoWork(); });
if (rewinded) {
goal_reached = true;
Fiber::YieldTo(fiber1, *thread_fiber);
@@ -352,11 +303,6 @@ public:
bool rewinded;
};
-static void WorkControl4(void* control) {
- auto* test_control = static_cast<TestControl4*>(control);
- test_control->DoWork();
-}
-
TEST_CASE("Fibers::Rewind", "[common]") {
TestControl4 test_control{};
test_control.Execute();
diff --git a/src/tests/common/host_memory.cpp b/src/tests/common/host_memory.cpp
index 2dc7b5d5e..e49d0a09f 100644
--- a/src/tests/common/host_memory.cpp
+++ b/src/tests/common/host_memory.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <catch2/catch.hpp>
diff --git a/src/tests/common/param_package.cpp b/src/tests/common/param_package.cpp
index e31ca3544..d036cc83a 100644
--- a/src/tests/common/param_package.cpp
+++ b/src/tests/common/param_package.cpp
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <catch2/catch.hpp>
#include <math.h>
diff --git a/src/tests/common/ring_buffer.cpp b/src/tests/common/ring_buffer.cpp
index 903626e4b..4f81b6e5e 100644
--- a/src/tests/common/ring_buffer.cpp
+++ b/src/tests/common/ring_buffer.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
diff --git a/src/tests/common/unique_function.cpp b/src/tests/common/unique_function.cpp
index aa6e86593..311272506 100644
--- a/src/tests/common/unique_function.cpp
+++ b/src/tests/common/unique_function.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp
index e0c66fa2e..7c432a63c 100644
--- a/src/tests/core/core_timing.cpp
+++ b/src/tests/core/core_timing.cpp
@@ -1,6 +1,5 @@
-// Copyright 2016 Dolphin Emulator Project / 2017 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <catch2/catch.hpp>
@@ -9,6 +8,7 @@
#include <chrono>
#include <cstdlib>
#include <memory>
+#include <optional>
#include <string>
#include "core/core.h"
@@ -24,13 +24,15 @@ std::bitset<CB_IDS.size()> callbacks_ran_flags;
u64 expected_callback = 0;
template <unsigned int IDX>
-void HostCallbackTemplate(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+std::optional<std::chrono::nanoseconds> HostCallbackTemplate(std::uintptr_t user_data, s64 time,
+ std::chrono::nanoseconds ns_late) {
static_assert(IDX < CB_IDS.size(), "IDX out of range");
callbacks_ran_flags.set(IDX);
REQUIRE(CB_IDS[IDX] == user_data);
REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]);
delays[IDX] = ns_late.count();
++expected_callback;
+ return std::nullopt;
}
struct ScopeInit final {
diff --git a/src/tests/core/internal_network/network.cpp b/src/tests/core/internal_network/network.cpp
new file mode 100644
index 000000000..164b0ff24
--- /dev/null
+++ b/src/tests/core/internal_network/network.cpp
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <catch2/catch.hpp>
+
+#include "core/internal_network/network.h"
+#include "core/internal_network/sockets.h"
+
+TEST_CASE("Network::Errors", "[core]") {
+ Network::NetworkInstance network_instance; // initialize network
+
+ Network::Socket socks[2];
+ for (Network::Socket& sock : socks) {
+ REQUIRE(sock.Initialize(Network::Domain::INET, Network::Type::STREAM,
+ Network::Protocol::TCP) == Network::Errno::SUCCESS);
+ }
+
+ Network::SockAddrIn addr{
+ Network::Domain::INET,
+ {127, 0, 0, 1},
+ 1, // hopefully nobody running this test has something listening on port 1
+ };
+ REQUIRE(socks[0].Connect(addr) == Network::Errno::CONNREFUSED);
+
+ std::vector<u8> message{1, 2, 3, 4};
+ REQUIRE(socks[1].Recv(0, message).second == Network::Errno::NOTCONN);
+}
diff --git a/src/tests/core/network/network.cpp b/src/tests/core/network/network.cpp
deleted file mode 100644
index b21ad8911..000000000
--- a/src/tests/core/network/network.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <catch2/catch.hpp>
-
-#include "core/network/network.h"
-#include "core/network/sockets.h"
-
-TEST_CASE("Network::Errors", "[core]") {
- Network::NetworkInstance network_instance; // initialize network
-
- Network::Socket socks[2];
- for (Network::Socket& sock : socks) {
- REQUIRE(sock.Initialize(Network::Domain::INET, Network::Type::STREAM,
- Network::Protocol::TCP) == Network::Errno::SUCCESS);
- }
-
- Network::SockAddrIn addr{
- Network::Domain::INET,
- {127, 0, 0, 1},
- 1, // hopefully nobody running this test has something listening on port 1
- };
- REQUIRE(socks[0].Connect(addr) == Network::Errno::CONNREFUSED);
-
- std::vector<u8> message{1, 2, 3, 4};
- REQUIRE(socks[1].Recv(0, message).second == Network::Errno::NOTCONN);
-}
diff --git a/src/tests/input_common/calibration_configuration_job.cpp b/src/tests/input_common/calibration_configuration_job.cpp
index 8c77d81e9..e5f698886 100644
--- a/src/tests/input_common/calibration_configuration_job.cpp
+++ b/src/tests/input_common/calibration_configuration_job.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <string>
diff --git a/src/tests/tests.cpp b/src/tests/tests.cpp
index 275b430d9..3f905c05c 100644
--- a/src/tests/tests.cpp
+++ b/src/tests/tests.cpp
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
diff --git a/src/tests/video_core/buffer_base.cpp b/src/tests/video_core/buffer_base.cpp
index 9f5a54de4..71121e42a 100644
--- a/src/tests/video_core/buffer_base.cpp
+++ b/src/tests/video_core/buffer_base.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <stdexcept>
#include <unordered_map>
@@ -23,8 +22,9 @@ constexpr VAddr c = 0x1328914000;
class RasterizerInterface {
public:
void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
- const u64 page_start{addr >> Core::Memory::PAGE_BITS};
- const u64 page_end{(addr + size + Core::Memory::PAGE_SIZE - 1) >> Core::Memory::PAGE_BITS};
+ const u64 page_start{addr >> Core::Memory::YUZU_PAGEBITS};
+ const u64 page_end{(addr + size + Core::Memory::YUZU_PAGESIZE - 1) >>
+ Core::Memory::YUZU_PAGEBITS};
for (u64 page = page_start; page < page_end; ++page) {
int& value = page_table[page];
value += delta;
@@ -38,7 +38,7 @@ public:
}
[[nodiscard]] int Count(VAddr addr) const noexcept {
- const auto it = page_table.find(addr >> Core::Memory::PAGE_BITS);
+ const auto it = page_table.find(addr >> Core::Memory::YUZU_PAGEBITS);
return it == page_table.end() ? 0 : it->second;
}
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 6a6325e38..40e6d1ec4 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -1,7 +1,10 @@
+# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
add_subdirectory(host_shaders)
if(LIBVA_FOUND)
- set_source_files_properties(command_classes/codecs/codec.cpp
+ set_source_files_properties(host1x/codecs/codec.cpp
PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES})
endif()
@@ -12,26 +15,14 @@ add_library(video_core STATIC
buffer_cache/buffer_cache.h
cdma_pusher.cpp
cdma_pusher.h
- command_classes/codecs/codec.cpp
- command_classes/codecs/codec.h
- command_classes/codecs/h264.cpp
- command_classes/codecs/h264.h
- command_classes/codecs/vp8.cpp
- command_classes/codecs/vp8.h
- command_classes/codecs/vp9.cpp
- command_classes/codecs/vp9.h
- command_classes/codecs/vp9_types.h
- command_classes/host1x.cpp
- command_classes/host1x.h
- command_classes/nvdec.cpp
- command_classes/nvdec.h
- command_classes/nvdec_common.h
- command_classes/sync_manager.cpp
- command_classes/sync_manager.h
- command_classes/vic.cpp
- command_classes/vic.h
compatible_formats.cpp
compatible_formats.h
+ control/channel_state.cpp
+ control/channel_state.h
+ control/channel_state_cache.cpp
+ control/channel_state_cache.h
+ control/scheduler.cpp
+ control/scheduler.h
delayed_destruction_ring.h
dirty_flags.cpp
dirty_flags.h
@@ -51,7 +42,31 @@ add_library(video_core STATIC
engines/maxwell_3d.h
engines/maxwell_dma.cpp
engines/maxwell_dma.h
+ engines/puller.cpp
+ engines/puller.h
framebuffer_config.h
+ host1x/codecs/codec.cpp
+ host1x/codecs/codec.h
+ host1x/codecs/h264.cpp
+ host1x/codecs/h264.h
+ host1x/codecs/vp8.cpp
+ host1x/codecs/vp8.h
+ host1x/codecs/vp9.cpp
+ host1x/codecs/vp9.h
+ host1x/codecs/vp9_types.h
+ host1x/control.cpp
+ host1x/control.h
+ host1x/host1x.cpp
+ host1x/host1x.h
+ host1x/nvdec.cpp
+ host1x/nvdec.h
+ host1x/nvdec_common.h
+ host1x/sync_manager.cpp
+ host1x/sync_manager.h
+ host1x/syncpoint_manager.cpp
+ host1x/syncpoint_manager.h
+ host1x/vic.cpp
+ host1x/vic.h
macro/macro.cpp
macro/macro.h
macro/macro_hle.cpp
@@ -192,6 +207,7 @@ add_library(video_core STATIC
texture_cache/render_targets.h
texture_cache/samples_helper.h
texture_cache/slot_vector.h
+ texture_cache/texture_cache.cpp
texture_cache/texture_cache.h
texture_cache/texture_cache_base.h
texture_cache/types.h
@@ -258,10 +274,6 @@ if (MSVC)
target_compile_options(video_core PRIVATE
/we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
/we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data
- /we4456 # Declaration of 'identifier' hides previous local declaration
- /we4457 # Declaration of 'identifier' hides function parameter
- /we4458 # Declaration of 'identifier' hides class member
- /we4459 # Declaration of 'identifier' hides global declaration
)
else()
target_compile_options(video_core PRIVATE
@@ -269,7 +281,6 @@ else()
-Wno-error=sign-conversion
-Werror=pessimizing-move
-Werror=redundant-move
- -Werror=shadow
-Werror=type-limits
$<$<CXX_COMPILER_ID:GNU>:-Werror=class-memaccess>
@@ -277,3 +288,7 @@ else()
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
)
endif()
+
+if (ARCHITECTURE_x86_64)
+ target_link_libraries(video_core PRIVATE dynarmic)
+endif()
diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h
index be2113f5a..f9a6472cf 100644
--- a/src/video_core/buffer_cache/buffer_base.h
+++ b/src/video_core/buffer_cache/buffer_base.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -13,6 +12,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/div_ceil.h"
+#include "common/settings.h"
#include "core/memory.h"
namespace VideoCommon {
@@ -37,7 +37,7 @@ struct NullBufferParams {};
template <class RasterizerInterface>
class BufferBase {
static constexpr u64 PAGES_PER_WORD = 64;
- static constexpr u64 BYTES_PER_PAGE = Core::Memory::PAGE_SIZE;
+ static constexpr u64 BYTES_PER_PAGE = Core::Memory::YUZU_PAGESIZE;
static constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE;
/// Vector tracking modified pages tightly packed with small vector optimization
@@ -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,9 @@ public:
NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits);
untracked_words[word_index] |= cached_bits;
cpu_words[word_index] |= cached_bits;
+ if (!Settings::values.use_pessimistic_flushes) {
+ cached_words[word_index] = 0;
+ }
}
}
diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp
index ab32294c8..a16308b60 100644
--- a/src/video_core/buffer_cache/buffer_cache.cpp
+++ b/src/video_core/buffer_cache/buffer_cache.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/microprofile.h"
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index fa26eb8b0..8e26b3f95 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -1,17 +1,14 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <array>
-#include <deque>
#include <memory>
#include <mutex>
#include <numeric>
#include <span>
-#include <unordered_map>
#include <vector>
#include <boost/container/small_vector.hpp>
@@ -22,10 +19,10 @@
#include "common/literals.h"
#include "common/lru_cache.h"
#include "common/microprofile.h"
-#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/memory.h"
#include "video_core/buffer_cache/buffer_base.h"
+#include "video_core/control/channel_state_cache.h"
#include "video_core/delayed_destruction_ring.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/kepler_compute.h"
@@ -59,12 +56,12 @@ using UniformBufferSizes = std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFE
using ComputeUniformBufferSizes = std::array<u32, NUM_COMPUTE_UNIFORM_BUFFERS>;
template <typename P>
-class BufferCache {
+class BufferCache : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
// Page size for caching purposes.
// This is unrelated to the CPU page size and it can be changed as it seems optimal.
- static constexpr u32 PAGE_BITS = 16;
- static constexpr u64 PAGE_SIZE = u64{1} << PAGE_BITS;
+ static constexpr u32 YUZU_PAGEBITS = 16;
+ static constexpr u64 YUZU_PAGESIZE = u64{1} << YUZU_PAGEBITS;
static constexpr bool IS_OPENGL = P::IS_OPENGL;
static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS =
@@ -78,8 +75,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;
@@ -118,10 +116,7 @@ public:
static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
- Tegra::Engines::Maxwell3D& maxwell3d_,
- Tegra::Engines::KeplerCompute& kepler_compute_,
- Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
- Runtime& runtime_);
+ Core::Memory::Memory& cpu_memory_, Runtime& runtime_);
void TickFrame();
@@ -131,7 +126,7 @@ public:
void DownloadMemory(VAddr cpu_addr, u64 size);
- bool InlineMemory(VAddr dest_address, size_t copy_size, std::span<u8> inlined_buffer);
+ bool InlineMemory(VAddr dest_address, size_t copy_size, std::span<const u8> inlined_buffer);
void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size);
@@ -218,8 +213,8 @@ private:
template <typename Func>
void ForEachBufferInRange(VAddr cpu_addr, u64 size, Func&& func) {
- const u64 page_end = Common::DivCeil(cpu_addr + size, PAGE_SIZE);
- for (u64 page = cpu_addr >> PAGE_BITS; page < page_end;) {
+ const u64 page_end = Common::DivCeil(cpu_addr + size, YUZU_PAGESIZE);
+ for (u64 page = cpu_addr >> YUZU_PAGEBITS; page < page_end;) {
const BufferId buffer_id = page_table[page];
if (!buffer_id) {
++page;
@@ -229,7 +224,7 @@ private:
func(buffer_id, buffer);
const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes();
- page = Common::DivCeil(end_addr, PAGE_SIZE);
+ page = Common::DivCeil(end_addr, YUZU_PAGESIZE);
}
}
@@ -264,8 +259,8 @@ private:
}
static bool IsRangeGranular(VAddr cpu_addr, size_t size) {
- return (cpu_addr & ~Core::Memory::PAGE_MASK) ==
- ((cpu_addr + size) & ~Core::Memory::PAGE_MASK);
+ return (cpu_addr & ~Core::Memory::YUZU_PAGEMASK) ==
+ ((cpu_addr + size) & ~Core::Memory::YUZU_PAGEMASK);
}
void RunGarbageCollector();
@@ -355,7 +350,7 @@ private:
void NotifyBufferDeletion();
- [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr) const;
+ [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr, bool is_written = false) const;
[[nodiscard]] TextureBufferBinding GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size,
PixelFormat format);
@@ -369,9 +364,6 @@ private:
void ClearDownload(IntervalType subtract_interval);
VideoCore::RasterizerInterface& rasterizer;
- Tegra::Engines::Maxwell3D& maxwell3d;
- Tegra::Engines::KeplerCompute& kepler_compute;
- Tegra::MemoryManager& gpu_memory;
Core::Memory::Memory& cpu_memory;
SlotVector<Buffer> slot_buffers;
@@ -438,26 +430,43 @@ 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;
+ std::array<BufferId, ((1ULL << 39) >> YUZU_PAGEBITS)> page_table;
};
template <class P>
BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_,
- Tegra::Engines::Maxwell3D& maxwell3d_,
- Tegra::Engines::KeplerCompute& kepler_compute_,
- Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
- Runtime& runtime_)
- : runtime{runtime_}, rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
- kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_}, cpu_memory{cpu_memory_} {
+ Core::Memory::Memory& cpu_memory_, Runtime& runtime_)
+ : runtime{runtime_}, rasterizer{rasterizer_}, cpu_memory{cpu_memory_} {
// 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) {
@@ -488,7 +497,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;
@@ -529,8 +542,8 @@ void BufferCache<P>::ClearDownload(IntervalType subtract_interval) {
template <class P>
bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount) {
- const std::optional<VAddr> cpu_src_address = gpu_memory.GpuToCpuAddress(src_address);
- const std::optional<VAddr> cpu_dest_address = gpu_memory.GpuToCpuAddress(dest_address);
+ const std::optional<VAddr> cpu_src_address = gpu_memory->GpuToCpuAddress(src_address);
+ const std::optional<VAddr> cpu_dest_address = gpu_memory->GpuToCpuAddress(dest_address);
if (!cpu_src_address || !cpu_dest_address) {
return false;
}
@@ -588,7 +601,7 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
template <class P>
bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
- const std::optional<VAddr> cpu_dst_address = gpu_memory.GpuToCpuAddress(dst_address);
+ const std::optional<VAddr> cpu_dst_address = gpu_memory->GpuToCpuAddress(dst_address);
if (!cpu_dst_address) {
return false;
}
@@ -612,7 +625,7 @@ bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
template <class P>
void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
u32 size) {
- const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
const Binding binding{
.cpu_addr = *cpu_addr,
.size = size,
@@ -650,7 +663,7 @@ void BufferCache<P>::BindHostGeometryBuffers(bool is_indexed) {
if (is_indexed) {
BindHostIndexBuffer();
} else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) {
runtime.BindQuadArrayIndexBuffer(regs.vertex_buffer.first, regs.vertex_buffer.count);
}
@@ -710,9 +723,9 @@ void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index,
enabled_storage_buffers[stage] |= 1U << ssbo_index;
written_storage_buffers[stage] |= (is_written ? 1U : 0U) << ssbo_index;
- const auto& cbufs = maxwell3d.state.shader_stages[stage];
+ const auto& cbufs = maxwell3d->state.shader_stages[stage];
const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
- storage_buffers[stage][ssbo_index] = StorageBufferBinding(ssbo_addr);
+ storage_buffers[stage][ssbo_index] = StorageBufferBinding(ssbo_addr, is_written);
}
template <class P>
@@ -747,12 +760,12 @@ void BufferCache<P>::BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index,
enabled_compute_storage_buffers |= 1U << ssbo_index;
written_compute_storage_buffers |= (is_written ? 1U : 0U) << ssbo_index;
- const auto& launch_desc = kepler_compute.launch_description;
+ const auto& launch_desc = kepler_compute->launch_description;
ASSERT(((launch_desc.const_buffer_enable_mask >> cbuf_index) & 1) != 0);
const auto& cbufs = launch_desc.const_buffer_config;
const GPUVAddr ssbo_addr = cbufs[cbuf_index].Address() + cbuf_offset;
- compute_storage_buffers[ssbo_index] = StorageBufferBinding(ssbo_addr);
+ compute_storage_buffers[ssbo_index] = StorageBufferBinding(ssbo_addr, is_written);
}
template <class P>
@@ -813,6 +826,19 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
const bool is_accuracy_normal =
Settings::values.gpu_accuracy.GetValue() == Settings::GPUAccuracy::Normal;
+ auto it = committed_ranges.begin();
+ while (it != committed_ranges.end()) {
+ auto& current_intervals = *it;
+ auto next_it = std::next(it);
+ while (next_it != committed_ranges.end()) {
+ for (auto& interval : *next_it) {
+ current_intervals.subtract(interval);
+ }
+ next_it++;
+ }
+ it++;
+ }
+
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads;
u64 total_size_bytes = 0;
u64 largest_copy = 0;
@@ -903,8 +929,8 @@ void BufferCache<P>::PopAsyncFlushes() {}
template <class P>
bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) {
- const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE);
- for (u64 page = addr >> PAGE_BITS; page < page_end;) {
+ const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE);
+ for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) {
const BufferId image_id = page_table[page];
if (!image_id) {
++page;
@@ -915,7 +941,7 @@ bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) {
return true;
}
const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes();
- page = Common::DivCeil(end_addr, PAGE_SIZE);
+ page = Common::DivCeil(end_addr, YUZU_PAGESIZE);
}
return false;
}
@@ -923,8 +949,8 @@ bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) {
template <class P>
bool BufferCache<P>::IsRegionRegistered(VAddr addr, size_t size) {
const VAddr end_addr = addr + size;
- const u64 page_end = Common::DivCeil(end_addr, PAGE_SIZE);
- for (u64 page = addr >> PAGE_BITS; page < page_end;) {
+ const u64 page_end = Common::DivCeil(end_addr, YUZU_PAGESIZE);
+ for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) {
const BufferId buffer_id = page_table[page];
if (!buffer_id) {
++page;
@@ -936,15 +962,15 @@ bool BufferCache<P>::IsRegionRegistered(VAddr addr, size_t size) {
if (buf_start_addr < end_addr && addr < buf_end_addr) {
return true;
}
- page = Common::DivCeil(end_addr, PAGE_SIZE);
+ page = Common::DivCeil(end_addr, YUZU_PAGESIZE);
}
return false;
}
template <class P>
bool BufferCache<P>::IsRegionCpuModified(VAddr addr, size_t size) {
- const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE);
- for (u64 page = addr >> PAGE_BITS; page < page_end;) {
+ const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE);
+ for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) {
const BufferId image_id = page_table[page];
if (!image_id) {
++page;
@@ -955,7 +981,7 @@ bool BufferCache<P>::IsRegionCpuModified(VAddr addr, size_t size) {
return true;
}
const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes();
- page = Common::DivCeil(end_addr, PAGE_SIZE);
+ page = Common::DivCeil(end_addr, YUZU_PAGESIZE);
}
return false;
}
@@ -968,19 +994,19 @@ void BufferCache<P>::BindHostIndexBuffer() {
const u32 size = index_buffer.size;
SynchronizeBuffer(buffer, index_buffer.cpu_addr, size);
if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
- const u32 new_offset = offset + maxwell3d.regs.index_array.first *
- maxwell3d.regs.index_array.FormatSizeInBytes();
+ const u32 new_offset = offset + maxwell3d->regs.index_array.first *
+ maxwell3d->regs.index_array.FormatSizeInBytes();
runtime.BindIndexBuffer(buffer, new_offset, size);
} else {
- runtime.BindIndexBuffer(maxwell3d.regs.draw.topology, maxwell3d.regs.index_array.format,
- maxwell3d.regs.index_array.first, maxwell3d.regs.index_array.count,
- buffer, offset, size);
+ runtime.BindIndexBuffer(maxwell3d->regs.draw.topology, maxwell3d->regs.index_array.format,
+ maxwell3d->regs.index_array.first,
+ maxwell3d->regs.index_array.count, buffer, offset, size);
}
}
template <class P>
void BufferCache<P>::BindHostVertexBuffers() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
const Binding& binding = vertex_buffers[index];
Buffer& buffer = slot_buffers[binding.buffer_id];
@@ -991,7 +1017,7 @@ void BufferCache<P>::BindHostVertexBuffers() {
}
flags[Dirty::VertexBuffer0 + index] = false;
- const u32 stride = maxwell3d.regs.vertex_array[index].stride;
+ const u32 stride = maxwell3d->regs.vertex_array[index].stride;
const u32 offset = buffer.Offset(binding.cpu_addr);
runtime.BindVertexBuffer(index, buffer, offset, binding.size, stride);
}
@@ -1131,7 +1157,7 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
template <class P>
void BufferCache<P>::BindHostTransformFeedbackBuffers() {
- if (maxwell3d.regs.tfb_enabled == 0) {
+ if (maxwell3d->regs.tfb_enabled == 0) {
return;
}
for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
@@ -1216,16 +1242,19 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
template <class P>
void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
- if (is_indexed) {
- UpdateIndexBuffer();
- }
- UpdateVertexBuffers();
- UpdateTransformFeedbackBuffers();
- for (size_t stage = 0; stage < NUM_STAGES; ++stage) {
- UpdateUniformBuffers(stage);
- UpdateStorageBuffers(stage);
- UpdateTextureBuffers(stage);
- }
+ do {
+ has_deleted_buffers = false;
+ if (is_indexed) {
+ UpdateIndexBuffer();
+ }
+ UpdateVertexBuffers();
+ UpdateTransformFeedbackBuffers();
+ for (size_t stage = 0; stage < NUM_STAGES; ++stage) {
+ UpdateUniformBuffers(stage);
+ UpdateStorageBuffers(stage);
+ UpdateTextureBuffers(stage);
+ }
+ } while (has_deleted_buffers);
}
template <class P>
@@ -1239,8 +1268,8 @@ template <class P>
void BufferCache<P>::UpdateIndexBuffer() {
// We have to check for the dirty flags and index count
// The index count is currently changed without updating the dirty flags
- const auto& index_array = maxwell3d.regs.index_array;
- auto& flags = maxwell3d.dirty.flags;
+ const auto& index_array = maxwell3d->regs.index_array;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::IndexBuffer] && last_index_count == index_array.count) {
return;
}
@@ -1249,7 +1278,7 @@ void BufferCache<P>::UpdateIndexBuffer() {
const GPUVAddr gpu_addr_begin = index_array.StartAddress();
const GPUVAddr gpu_addr_end = index_array.EndAddress();
- const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr_begin);
+ const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
const u32 draw_size = (index_array.count + index_array.first) * index_array.FormatSizeInBytes();
const u32 size = std::min(address_size, draw_size);
@@ -1266,8 +1295,8 @@ void BufferCache<P>::UpdateIndexBuffer() {
template <class P>
void BufferCache<P>::UpdateVertexBuffers() {
- auto& flags = maxwell3d.dirty.flags;
- if (!maxwell3d.dirty.flags[Dirty::VertexBuffers]) {
+ auto& flags = maxwell3d->dirty.flags;
+ if (!maxwell3d->dirty.flags[Dirty::VertexBuffers]) {
return;
}
flags[Dirty::VertexBuffers] = false;
@@ -1279,20 +1308,25 @@ void BufferCache<P>::UpdateVertexBuffers() {
template <class P>
void BufferCache<P>::UpdateVertexBuffer(u32 index) {
- if (!maxwell3d.dirty.flags[Dirty::VertexBuffer0 + index]) {
+ if (!maxwell3d->dirty.flags[Dirty::VertexBuffer0 + index]) {
return;
}
- const auto& array = maxwell3d.regs.vertex_array[index];
- const auto& limit = maxwell3d.regs.vertex_array_limit[index];
+ const auto& array = maxwell3d->regs.vertex_array[index];
+ const auto& limit = maxwell3d->regs.vertex_array_limit[index];
const GPUVAddr gpu_addr_begin = array.StartAddress();
const GPUVAddr gpu_addr_end = limit.LimitAddress() + 1;
- const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr_begin);
- const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
- const u32 size = address_size; // TODO: Analyze stride and number of vertices
- if (array.enable == 0 || size == 0 || !cpu_addr) {
+ const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
+ u32 address_size = static_cast<u32>(
+ std::min(gpu_addr_end - gpu_addr_begin, static_cast<u64>(std::numeric_limits<u32>::max())));
+ if (array.enable == 0 || address_size == 0 || !cpu_addr) {
vertex_buffers[index] = NULL_BINDING;
return;
}
+ if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end)) {
+ address_size =
+ static_cast<u32>(gpu_memory->MaxContinousRange(gpu_addr_begin, address_size));
+ }
+ const u32 size = address_size; // TODO: Analyze stride and number of vertices
vertex_buffers[index] = Binding{
.cpu_addr = *cpu_addr,
.size = size,
@@ -1346,7 +1380,7 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
template <class P>
void BufferCache<P>::UpdateTransformFeedbackBuffers() {
- if (maxwell3d.regs.tfb_enabled == 0) {
+ if (maxwell3d->regs.tfb_enabled == 0) {
return;
}
for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
@@ -1356,10 +1390,10 @@ void BufferCache<P>::UpdateTransformFeedbackBuffers() {
template <class P>
void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
- const auto& binding = maxwell3d.regs.tfb_bindings[index];
+ const auto& binding = maxwell3d->regs.tfb_bindings[index];
const GPUVAddr gpu_addr = binding.Address() + binding.buffer_offset;
const u32 size = binding.buffer_size;
- const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
if (binding.buffer_enable == 0 || size == 0 || !cpu_addr) {
transform_feedback_buffers[index] = NULL_BINDING;
return;
@@ -1378,10 +1412,10 @@ void BufferCache<P>::UpdateComputeUniformBuffers() {
ForEachEnabledBit(enabled_compute_uniform_buffer_mask, [&](u32 index) {
Binding& binding = compute_uniform_buffers[index];
binding = NULL_BINDING;
- const auto& launch_desc = kepler_compute.launch_description;
+ const auto& launch_desc = kepler_compute->launch_description;
if (((launch_desc.const_buffer_enable_mask >> index) & 1) != 0) {
const auto& cbuf = launch_desc.const_buffer_config[index];
- const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(cbuf.Address());
+ const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(cbuf.Address());
if (cpu_addr) {
binding.cpu_addr = *cpu_addr;
binding.size = cbuf.size;
@@ -1436,7 +1470,7 @@ BufferId BufferCache<P>::FindBuffer(VAddr cpu_addr, u32 size) {
if (cpu_addr == 0) {
return NULL_BUFFER_ID;
}
- const u64 page = cpu_addr >> PAGE_BITS;
+ const u64 page = cpu_addr >> YUZU_PAGEBITS;
const BufferId buffer_id = page_table[page];
if (!buffer_id) {
return CreateBuffer(cpu_addr, size);
@@ -1457,8 +1491,9 @@ typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu
VAddr end = cpu_addr + wanted_size;
int stream_score = 0;
bool has_stream_leap = false;
- for (; cpu_addr >> PAGE_BITS < Common::DivCeil(end, PAGE_SIZE); cpu_addr += PAGE_SIZE) {
- const BufferId overlap_id = page_table[cpu_addr >> PAGE_BITS];
+ for (; cpu_addr >> YUZU_PAGEBITS < Common::DivCeil(end, YUZU_PAGESIZE);
+ cpu_addr += YUZU_PAGESIZE) {
+ const BufferId overlap_id = page_table[cpu_addr >> YUZU_PAGEBITS];
if (!overlap_id) {
continue;
}
@@ -1469,19 +1504,27 @@ typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu
overlap_ids.push_back(overlap_id);
overlap.Pick();
const VAddr overlap_cpu_addr = overlap.CpuAddr();
- if (overlap_cpu_addr < begin) {
+ const bool expands_left = overlap_cpu_addr < begin;
+ if (expands_left) {
cpu_addr = begin = overlap_cpu_addr;
}
- end = std::max(end, overlap_cpu_addr + overlap.SizeBytes());
-
+ const VAddr overlap_end = overlap_cpu_addr + overlap.SizeBytes();
+ const bool expands_right = overlap_end > end;
+ if (overlap_end > end) {
+ end = overlap_end;
+ }
stream_score += overlap.StreamScore();
if (stream_score > STREAM_LEAP_THRESHOLD && !has_stream_leap) {
// When this memory region has been joined a bunch of times, we assume it's being used
// as a stream buffer. Increase the size to skip constantly recreating buffers.
has_stream_leap = true;
- begin -= PAGE_SIZE * 256;
- cpu_addr = begin;
- end += PAGE_SIZE * 256;
+ if (expands_right) {
+ begin -= YUZU_PAGESIZE * 256;
+ cpu_addr = begin;
+ }
+ if (expands_left) {
+ end += YUZU_PAGESIZE * 256;
+ }
}
}
return OverlapResult{
@@ -1522,6 +1565,8 @@ BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) {
const OverlapResult overlap = ResolveOverlaps(cpu_addr, wanted_size);
const u32 size = static_cast<u32>(overlap.end - overlap.begin);
const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size);
+ auto& new_buffer = slot_buffers[new_buffer_id];
+ runtime.ClearBuffer(new_buffer, 0, new_buffer.SizeBytes(), 0);
for (const BufferId overlap_id : overlap.ids) {
JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap);
}
@@ -1554,8 +1599,8 @@ void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
}
const VAddr cpu_addr_begin = buffer.CpuAddr();
const VAddr cpu_addr_end = cpu_addr_begin + size;
- const u64 page_begin = cpu_addr_begin / PAGE_SIZE;
- const u64 page_end = Common::DivCeil(cpu_addr_end, PAGE_SIZE);
+ const u64 page_begin = cpu_addr_begin / YUZU_PAGESIZE;
+ const u64 page_end = Common::DivCeil(cpu_addr_end, YUZU_PAGESIZE);
for (u64 page = page_begin; page != page_end; ++page) {
if constexpr (insert) {
page_table[page] = buffer_id;
@@ -1650,7 +1695,7 @@ void BufferCache<P>::MappedUploadMemory(Buffer& buffer, u64 total_size_bytes,
template <class P>
bool BufferCache<P>::InlineMemory(VAddr dest_address, size_t copy_size,
- std::span<u8> inlined_buffer) {
+ std::span<const u8> inlined_buffer) {
const bool is_dirty = IsRegionRegistered(dest_address, copy_size);
if (!is_dirty) {
return false;
@@ -1786,7 +1831,7 @@ void BufferCache<P>::NotifyBufferDeletion() {
dirty_uniform_buffers.fill(~u32{0});
uniform_buffer_binding_sizes.fill({});
}
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
flags[Dirty::IndexBuffer] = true;
flags[Dirty::VertexBuffers] = true;
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
@@ -1796,16 +1841,18 @@ void BufferCache<P>::NotifyBufferDeletion() {
}
template <class P>
-typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr) const {
- const GPUVAddr gpu_addr = gpu_memory.Read<u64>(ssbo_addr);
- const u32 size = gpu_memory.Read<u32>(ssbo_addr + 8);
- const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr,
+ bool is_written) const {
+ const GPUVAddr gpu_addr = gpu_memory->Read<u64>(ssbo_addr);
+ const u32 size = gpu_memory->Read<u32>(ssbo_addr + 8);
+ const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
if (!cpu_addr || size == 0) {
return NULL_BINDING;
}
+ const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, Core::Memory::YUZU_PAGESIZE);
const Binding binding{
.cpu_addr = *cpu_addr,
- .size = size,
+ .size = is_written ? size : static_cast<u32>(cpu_end - *cpu_addr),
.buffer_id = BufferId{},
};
return binding;
@@ -1814,7 +1861,7 @@ typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr s
template <class P>
typename BufferCache<P>::TextureBufferBinding BufferCache<P>::GetTextureBufferBinding(
GPUVAddr gpu_addr, u32 size, PixelFormat format) {
- const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
TextureBufferBinding binding;
if (!cpu_addr || size == 0) {
binding.cpu_addr = 0;
diff --git a/src/video_core/cdma_pusher.cpp b/src/video_core/cdma_pusher.cpp
index a8c4b4415..28a2d2090 100644
--- a/src/video_core/cdma_pusher.cpp
+++ b/src/video_core/cdma_pusher.cpp
@@ -1,40 +1,23 @@
-// MIT License
-//
-// Copyright (c) Ryujinx Team and Contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
-// associated documentation files (the "Software"), to deal in the Software without restriction,
-// including without limitation the rights to use, copy, modify, merge, publish, distribute,
-// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all copies or
-// substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
-// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
+// SPDX-FileCopyrightText: Ryujinx Team and Contributors
+// SPDX-License-Identifier: MIT
#include <bit>
-#include "command_classes/host1x.h"
-#include "command_classes/nvdec.h"
-#include "command_classes/vic.h"
#include "video_core/cdma_pusher.h"
-#include "video_core/command_classes/nvdec_common.h"
-#include "video_core/command_classes/sync_manager.h"
#include "video_core/engines/maxwell_3d.h"
-#include "video_core/gpu.h"
+#include "video_core/host1x/control.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/host1x/nvdec.h"
+#include "video_core/host1x/nvdec_common.h"
+#include "video_core/host1x/sync_manager.h"
+#include "video_core/host1x/vic.h"
#include "video_core/memory_manager.h"
namespace Tegra {
-CDmaPusher::CDmaPusher(GPU& gpu_)
- : gpu{gpu_}, nvdec_processor(std::make_shared<Nvdec>(gpu)),
- vic_processor(std::make_unique<Vic>(gpu, nvdec_processor)),
- host1x_processor(std::make_unique<Host1x>(gpu)),
- sync_manager(std::make_unique<SyncptIncrManager>(gpu)) {}
+CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_)
+ : host1x{host1x_}, nvdec_processor(std::make_shared<Host1x::Nvdec>(host1x)),
+ vic_processor(std::make_unique<Host1x::Vic>(host1x, nvdec_processor)),
+ host1x_processor(std::make_unique<Host1x::Control>(host1x)),
+ sync_manager(std::make_unique<Host1x::SyncptIncrManager>(host1x)) {}
CDmaPusher::~CDmaPusher() = default;
@@ -128,16 +111,17 @@ void CDmaPusher::ExecuteCommand(u32 state_offset, u32 data) {
case ThiMethod::SetMethod1:
LOG_DEBUG(Service_NVDRV, "VIC method 0x{:X}, Args=({})",
static_cast<u32>(vic_thi_state.method_0), data);
- vic_processor->ProcessMethod(static_cast<Vic::Method>(vic_thi_state.method_0), data);
+ vic_processor->ProcessMethod(static_cast<Host1x::Vic::Method>(vic_thi_state.method_0),
+ data);
break;
default:
break;
}
break;
- case ChClassId::Host1x:
+ case ChClassId::Control:
// This device is mainly for syncpoint synchronization
LOG_DEBUG(Service_NVDRV, "Host1X Class Method");
- host1x_processor->ProcessMethod(static_cast<Host1x::Method>(offset), data);
+ host1x_processor->ProcessMethod(static_cast<Host1x::Control::Method>(offset), data);
break;
default:
UNIMPLEMENTED_MSG("Current class not implemented {:X}", static_cast<u32>(current_class));
diff --git a/src/video_core/cdma_pusher.h b/src/video_core/cdma_pusher.h
index 87b49d6ea..83112dfce 100644
--- a/src/video_core/cdma_pusher.h
+++ b/src/video_core/cdma_pusher.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,15 +7,18 @@
#include <vector>
#include "common/bit_field.h"
+#include "common/common_funcs.h"
#include "common/common_types.h"
namespace Tegra {
-class GPU;
+namespace Host1x {
+class Control;
class Host1x;
class Nvdec;
class SyncptIncrManager;
class Vic;
+} // namespace Host1x
enum class ChSubmissionMode : u32 {
SetClass = 0,
@@ -30,7 +32,7 @@ enum class ChSubmissionMode : u32 {
enum class ChClassId : u32 {
NoClass = 0x0,
- Host1x = 0x1,
+ Control = 0x1,
VideoEncodeMpeg = 0x20,
VideoEncodeNvEnc = 0x21,
VideoStreamingVi = 0x30,
@@ -88,7 +90,7 @@ enum class ThiMethod : u32 {
class CDmaPusher {
public:
- explicit CDmaPusher(GPU& gpu_);
+ explicit CDmaPusher(Host1x::Host1x& host1x);
~CDmaPusher();
/// Process the command entry
@@ -101,11 +103,11 @@ private:
/// Write arguments value to the ThiRegisters member at the specified offset
void ThiStateWrite(ThiRegisters& state, u32 offset, u32 argument);
- GPU& gpu;
- std::shared_ptr<Tegra::Nvdec> nvdec_processor;
- std::unique_ptr<Tegra::Vic> vic_processor;
- std::unique_ptr<Tegra::Host1x> host1x_processor;
- std::unique_ptr<SyncptIncrManager> sync_manager;
+ Host1x::Host1x& host1x;
+ std::shared_ptr<Tegra::Host1x::Nvdec> nvdec_processor;
+ std::unique_ptr<Tegra::Host1x::Vic> vic_processor;
+ std::unique_ptr<Tegra::Host1x::Control> host1x_processor;
+ std::unique_ptr<Host1x::SyncptIncrManager> sync_manager;
ChClassId current_class{};
ThiRegisters vic_thi_state{};
ThiRegisters nvdec_thi_state{};
diff --git a/src/video_core/command_classes/codecs/codec.cpp b/src/video_core/command_classes/codecs/codec.cpp
deleted file mode 100644
index 04d0f3a2f..000000000
--- a/src/video_core/command_classes/codecs/codec.cpp
+++ /dev/null
@@ -1,305 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <cstdio>
-#include <fstream>
-#include <vector>
-#include "common/assert.h"
-#include "common/settings.h"
-#include "video_core/command_classes/codecs/codec.h"
-#include "video_core/command_classes/codecs/h264.h"
-#include "video_core/command_classes/codecs/vp8.h"
-#include "video_core/command_classes/codecs/vp9.h"
-#include "video_core/gpu.h"
-#include "video_core/memory_manager.h"
-
-extern "C" {
-#include <libavutil/opt.h>
-#ifdef LIBVA_FOUND
-// for querying VAAPI driver information
-#include <libavutil/hwcontext_vaapi.h>
-#endif
-}
-
-namespace Tegra {
-namespace {
-constexpr AVPixelFormat PREFERRED_GPU_FMT = AV_PIX_FMT_NV12;
-constexpr AVPixelFormat PREFERRED_CPU_FMT = AV_PIX_FMT_YUV420P;
-constexpr std::array PREFERRED_GPU_DECODERS = {
- AV_HWDEVICE_TYPE_CUDA,
-#ifdef _WIN32
- AV_HWDEVICE_TYPE_D3D11VA,
- AV_HWDEVICE_TYPE_DXVA2,
-#elif defined(__unix__)
- AV_HWDEVICE_TYPE_VAAPI,
- AV_HWDEVICE_TYPE_VDPAU,
-#endif
- // last resort for Linux Flatpak (w/ NVIDIA)
- AV_HWDEVICE_TYPE_VULKAN,
-};
-
-void AVPacketDeleter(AVPacket* ptr) {
- av_packet_free(&ptr);
-}
-
-using AVPacketPtr = std::unique_ptr<AVPacket, decltype(&AVPacketDeleter)>;
-
-AVPixelFormat GetGpuFormat(AVCodecContext* av_codec_ctx, const AVPixelFormat* pix_fmts) {
- for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
- if (*p == av_codec_ctx->pix_fmt) {
- return av_codec_ctx->pix_fmt;
- }
- }
- LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU");
- av_buffer_unref(&av_codec_ctx->hw_device_ctx);
- av_codec_ctx->pix_fmt = PREFERRED_CPU_FMT;
- return PREFERRED_CPU_FMT;
-}
-} // namespace
-
-void AVFrameDeleter(AVFrame* ptr) {
- av_frame_free(&ptr);
-}
-
-Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs)
- : gpu(gpu_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(gpu)),
- vp8_decoder(std::make_unique<Decoder::VP8>(gpu)),
- vp9_decoder(std::make_unique<Decoder::VP9>(gpu)) {}
-
-Codec::~Codec() {
- if (!initialized) {
- return;
- }
- // Free libav memory
- avcodec_free_context(&av_codec_ctx);
- 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();
- for (const auto& type : PREFERRED_GPU_DECODERS) {
- if (std::none_of(supported_contexts.begin(), supported_contexts.end(),
- [&type](const auto& context) { return context == type; })) {
- LOG_DEBUG(Service_NVDRV, "{} explicitly unsupported", av_hwdevice_get_type_name(type));
- continue;
- }
- 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 {}",
- av_hwdevice_get_type_name(type), hwdevice_res);
- continue;
- }
-#ifdef LIBVA_FOUND
- if (type == AV_HWDEVICE_TYPE_VAAPI) {
- // we need to determine if this is an impersonated VAAPI driver
- AVHWDeviceContext* hwctx =
- static_cast<AVHWDeviceContext*>(static_cast<void*>(av_gpu_decoder->data));
- AVVAAPIDeviceContext* vactx = static_cast<AVVAAPIDeviceContext*>(hwctx->hwctx);
- const char* vendor_name = vaQueryVendorString(vactx->display);
- if (strstr(vendor_name, "VDPAU backend")) {
- // VDPAU impersonated VAAPI impl's are super buggy, we need to skip them
- LOG_DEBUG(Service_NVDRV, "Skipping vdapu impersonated VAAPI driver");
- continue;
- } else {
- // according to some user testing, certain vaapi driver (Intel?) could be buggy
- // so let's log the driver name which may help the developers/supporters
- LOG_DEBUG(Service_NVDRV, "Using VAAPI driver: {}", vendor_name);
- }
- }
-#endif
- for (int i = 0;; i++) {
- const AVCodecHWConfig* config = avcodec_get_hw_config(av_codec, i);
- if (!config) {
- LOG_DEBUG(Service_NVDRV, "{} decoder does not support device type {}.",
- 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) {
- // 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;
- }
- LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
- return true;
- }
- }
- }
- return false;
-}
-
-void Codec::InitializeAvCodecContext() {
- av_codec_ctx = avcodec_alloc_context3(av_codec);
- av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
-}
-
-void Codec::InitializeGpuDecoder() {
- if (!CreateGpuAvDevice()) {
- av_buffer_unref(&av_gpu_decoder);
- return;
- }
- auto* hw_device_ctx = av_buffer_ref(av_gpu_decoder);
- ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed");
- av_codec_ctx->hw_device_ctx = hw_device_ctx;
- av_codec_ctx->get_format = GetGpuFormat;
-}
-
-void Codec::Initialize() {
- const AVCodecID codec = [&] {
- switch (current_codec) {
- case NvdecCommon::VideoCodec::H264:
- return AV_CODEC_ID_H264;
- case NvdecCommon::VideoCodec::VP8:
- return AV_CODEC_ID_VP8;
- case NvdecCommon::VideoCodec::VP9:
- return AV_CODEC_ID_VP9;
- default:
- UNIMPLEMENTED_MSG("Unknown codec {}", current_codec);
- return AV_CODEC_ID_NONE;
- }
- }();
- av_codec = avcodec_find_decoder(codec);
-
- InitializeAvCodecContext();
- if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::GPU) {
- InitializeGpuDecoder();
- }
- if (const int res = avcodec_open2(av_codec_ctx, av_codec, nullptr); res < 0) {
- LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed with result {}", res);
- avcodec_free_context(&av_codec_ctx);
- av_buffer_unref(&av_gpu_decoder);
- return;
- }
- if (!av_codec_ctx->hw_device_ctx) {
- LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding");
- }
- initialized = true;
-}
-
-void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
- if (current_codec != codec) {
- current_codec = codec;
- LOG_INFO(Service_NVDRV, "NVDEC video codec initialized to {}", GetCurrentCodecName());
- }
-}
-
-void Codec::Decode() {
- const bool is_first_frame = !initialized;
- if (is_first_frame) {
- Initialize();
- }
- if (!initialized) {
- return;
- }
- bool vp9_hidden_frame = false;
- const auto& frame_data = [&]() {
- switch (current_codec) {
- case Tegra::NvdecCommon::VideoCodec::H264:
- return h264_decoder->ComposeFrame(state, is_first_frame);
- case Tegra::NvdecCommon::VideoCodec::VP8:
- return vp8_decoder->ComposeFrame(state);
- case Tegra::NvdecCommon::VideoCodec::VP9:
- vp9_decoder->ComposeFrame(state);
- vp9_hidden_frame = vp9_decoder->WasFrameHidden();
- return vp9_decoder->GetFrameBytes();
- default:
- UNREACHABLE();
- return std::vector<u8>{};
- }
- }();
- AVPacketPtr packet{av_packet_alloc(), AVPacketDeleter};
- if (!packet) {
- LOG_ERROR(Service_NVDRV, "av_packet_alloc failed");
- return;
- }
- packet->data = const_cast<u8*>(frame_data.data());
- packet->size = static_cast<s32>(frame_data.size());
- if (const int res = avcodec_send_packet(av_codec_ctx, packet.get()); res != 0) {
- LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", res);
- return;
- }
- // Only receive/store visible frames
- if (vp9_hidden_frame) {
- return;
- }
- AVFramePtr initial_frame{av_frame_alloc(), AVFrameDeleter};
- AVFramePtr final_frame{nullptr, AVFrameDeleter};
- ASSERT_MSG(initial_frame, "av_frame_alloc initial_frame failed");
- if (const int ret = avcodec_receive_frame(av_codec_ctx, initial_frame.get()); ret) {
- LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret);
- return;
- }
- if (initial_frame->width == 0 || initial_frame->height == 0) {
- LOG_WARNING(Service_NVDRV, "Zero width or height in frame");
- return;
- }
- if (av_codec_ctx->hw_device_ctx) {
- final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
- ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed");
- // Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp
- // because Intel drivers crash unless using AV_PIX_FMT_NV12
- final_frame->format = PREFERRED_GPU_FMT;
- const int ret = av_hwframe_transfer_data(final_frame.get(), initial_frame.get(), 0);
- ASSERT_MSG(!ret, "av_hwframe_transfer_data error {}", ret);
- } else {
- final_frame = std::move(initial_frame);
- }
- if (final_frame->format != PREFERRED_CPU_FMT && final_frame->format != PREFERRED_GPU_FMT) {
- UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format);
- return;
- }
- av_frames.push(std::move(final_frame));
- if (av_frames.size() > 10) {
- LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame");
- av_frames.pop();
- }
-}
-
-AVFramePtr Codec::GetCurrentFrame() {
- // Sometimes VIC will request more frames than have been decoded.
- // in this case, return a nullptr and don't overwrite previous frame data
- if (av_frames.empty()) {
- return AVFramePtr{nullptr, AVFrameDeleter};
- }
- AVFramePtr frame = std::move(av_frames.front());
- av_frames.pop();
- return frame;
-}
-
-NvdecCommon::VideoCodec Codec::GetCurrentCodec() const {
- return current_codec;
-}
-
-std::string_view Codec::GetCurrentCodecName() const {
- switch (current_codec) {
- case NvdecCommon::VideoCodec::None:
- return "None";
- case NvdecCommon::VideoCodec::H264:
- return "H264";
- case NvdecCommon::VideoCodec::VP8:
- return "VP8";
- case NvdecCommon::VideoCodec::H265:
- return "H265";
- case NvdecCommon::VideoCodec::VP9:
- return "VP9";
- default:
- return "Unknown";
- }
-}
-} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/codec.h b/src/video_core/command_classes/codecs/codec.h
deleted file mode 100644
index de5672155..000000000
--- a/src/video_core/command_classes/codecs/codec.h
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <string_view>
-#include <queue>
-#include "common/common_types.h"
-#include "video_core/command_classes/nvdec_common.h"
-
-extern "C" {
-#if defined(__GNUC__) || defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wconversion"
-#endif
-#include <libavcodec/avcodec.h>
-#if defined(__GNUC__) || defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
-}
-
-namespace Tegra {
-class GPU;
-
-void AVFrameDeleter(AVFrame* ptr);
-using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>;
-
-namespace Decoder {
-class H264;
-class VP8;
-class VP9;
-} // namespace Decoder
-
-class Codec {
-public:
- explicit Codec(GPU& gpu, const NvdecCommon::NvdecRegisters& regs);
- ~Codec();
-
- /// Initialize the codec, returning success or failure
- void Initialize();
-
- /// Sets NVDEC video stream codec
- void SetTargetCodec(NvdecCommon::VideoCodec codec);
-
- /// Call decoders to construct headers, decode AVFrame with ffmpeg
- void Decode();
-
- /// Returns next decoded frame
- [[nodiscard]] AVFramePtr GetCurrentFrame();
-
- /// Returns the value of current_codec
- [[nodiscard]] NvdecCommon::VideoCodec GetCurrentCodec() const;
-
- /// Return name of the current codec
- [[nodiscard]] std::string_view GetCurrentCodecName() const;
-
-private:
- void InitializeAvCodecContext();
-
- void InitializeGpuDecoder();
-
- bool CreateGpuAvDevice();
-
- bool initialized{};
- NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None};
-
- const AVCodec* av_codec{nullptr};
- AVCodecContext* av_codec_ctx{nullptr};
- AVBufferRef* av_gpu_decoder{nullptr};
-
- GPU& gpu;
- const NvdecCommon::NvdecRegisters& state;
- std::unique_ptr<Decoder::H264> h264_decoder;
- std::unique_ptr<Decoder::VP8> vp8_decoder;
- std::unique_ptr<Decoder::VP9> vp9_decoder;
-
- std::queue<AVFramePtr> av_frames{};
-};
-
-} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/h264.cpp b/src/video_core/command_classes/codecs/h264.cpp
deleted file mode 100644
index 84f1fa938..000000000
--- a/src/video_core/command_classes/codecs/h264.cpp
+++ /dev/null
@@ -1,294 +0,0 @@
-// MIT License
-//
-// Copyright (c) Ryujinx Team and Contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
-// associated documentation files (the "Software"), to deal in the Software without restriction,
-// including without limitation the rights to use, copy, modify, merge, publish, distribute,
-// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all copies or
-// substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
-// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-#include <array>
-#include <bit>
-
-#include "common/settings.h"
-#include "video_core/command_classes/codecs/h264.h"
-#include "video_core/gpu.h"
-#include "video_core/memory_manager.h"
-
-namespace Tegra::Decoder {
-namespace {
-// ZigZag LUTs from libavcodec.
-constexpr std::array<u8, 64> zig_zag_direct{
- 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48,
- 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23,
- 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
-};
-
-constexpr std::array<u8, 16> zig_zag_scan{
- 0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4, 1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4,
- 1 + 2 * 4, 0 + 3 * 4, 1 + 3 * 4, 2 + 2 * 4, 3 + 1 * 4, 3 + 2 * 4, 2 + 3 * 4, 3 + 3 * 4,
-};
-} // Anonymous namespace
-
-H264::H264(GPU& gpu_) : gpu(gpu_) {}
-
-H264::~H264() = default;
-
-const std::vector<u8>& H264::ComposeFrame(const NvdecCommon::NvdecRegisters& state,
- bool is_first_frame) {
- H264DecoderContext context;
- gpu.MemoryManager().ReadBlock(state.picture_info_offset, &context, sizeof(H264DecoderContext));
-
- const s64 frame_number = context.h264_parameter_set.frame_number.Value();
- if (!is_first_frame && frame_number != 0) {
- frame.resize(context.stream_len);
- gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size());
- return frame;
- }
-
- // Encode header
- H264BitWriter writer{};
- writer.WriteU(1, 24);
- writer.WriteU(0, 1);
- writer.WriteU(3, 2);
- writer.WriteU(7, 5);
- writer.WriteU(100, 8);
- writer.WriteU(0, 8);
- writer.WriteU(31, 8);
- writer.WriteUe(0);
- const u32 chroma_format_idc =
- static_cast<u32>(context.h264_parameter_set.chroma_format_idc.Value());
- writer.WriteUe(chroma_format_idc);
- if (chroma_format_idc == 3) {
- writer.WriteBit(false);
- }
-
- writer.WriteUe(0);
- writer.WriteUe(0);
- writer.WriteBit(false); // QpprimeYZeroTransformBypassFlag
- writer.WriteBit(false); // Scaling matrix present flag
-
- writer.WriteUe(static_cast<u32>(context.h264_parameter_set.log2_max_frame_num_minus4.Value()));
-
- const auto order_cnt_type =
- static_cast<u32>(context.h264_parameter_set.pic_order_cnt_type.Value());
- writer.WriteUe(order_cnt_type);
- if (order_cnt_type == 0) {
- writer.WriteUe(context.h264_parameter_set.log2_max_pic_order_cnt_lsb_minus4);
- } else if (order_cnt_type == 1) {
- writer.WriteBit(context.h264_parameter_set.delta_pic_order_always_zero_flag != 0);
-
- writer.WriteSe(0);
- writer.WriteSe(0);
- writer.WriteUe(0);
- }
-
- const s32 pic_height = context.h264_parameter_set.frame_height_in_map_units /
- (context.h264_parameter_set.frame_mbs_only_flag ? 1 : 2);
-
- // TODO (ameerj): Where do we get this number, it seems to be particular for each stream
- const auto nvdec_decoding = Settings::values.nvdec_emulation.GetValue();
- const bool uses_gpu_decoding = nvdec_decoding == Settings::NvdecEmulation::GPU;
- const u32 max_num_ref_frames = uses_gpu_decoding ? 6u : 16u;
- writer.WriteUe(max_num_ref_frames);
- writer.WriteBit(false);
- writer.WriteUe(context.h264_parameter_set.pic_width_in_mbs - 1);
- writer.WriteUe(pic_height - 1);
- writer.WriteBit(context.h264_parameter_set.frame_mbs_only_flag != 0);
-
- if (!context.h264_parameter_set.frame_mbs_only_flag) {
- writer.WriteBit(context.h264_parameter_set.flags.mbaff_frame.Value() != 0);
- }
-
- writer.WriteBit(context.h264_parameter_set.flags.direct_8x8_inference.Value() != 0);
- writer.WriteBit(false); // Frame cropping flag
- writer.WriteBit(false); // VUI parameter present flag
-
- writer.End();
-
- // H264 PPS
- writer.WriteU(1, 24);
- writer.WriteU(0, 1);
- writer.WriteU(3, 2);
- writer.WriteU(8, 5);
-
- writer.WriteUe(0);
- writer.WriteUe(0);
-
- writer.WriteBit(context.h264_parameter_set.entropy_coding_mode_flag != 0);
- writer.WriteBit(false);
- writer.WriteUe(0);
- writer.WriteUe(context.h264_parameter_set.num_refidx_l0_default_active);
- writer.WriteUe(context.h264_parameter_set.num_refidx_l1_default_active);
- writer.WriteBit(context.h264_parameter_set.flags.weighted_pred.Value() != 0);
- writer.WriteU(static_cast<s32>(context.h264_parameter_set.weighted_bipred_idc.Value()), 2);
- s32 pic_init_qp = static_cast<s32>(context.h264_parameter_set.pic_init_qp_minus26.Value());
- writer.WriteSe(pic_init_qp);
- writer.WriteSe(0);
- s32 chroma_qp_index_offset =
- static_cast<s32>(context.h264_parameter_set.chroma_qp_index_offset.Value());
-
- writer.WriteSe(chroma_qp_index_offset);
- writer.WriteBit(context.h264_parameter_set.deblocking_filter_control_present_flag != 0);
- writer.WriteBit(context.h264_parameter_set.flags.constrained_intra_pred.Value() != 0);
- writer.WriteBit(context.h264_parameter_set.redundant_pic_cnt_present_flag != 0);
- writer.WriteBit(context.h264_parameter_set.transform_8x8_mode_flag != 0);
-
- writer.WriteBit(true);
-
- for (s32 index = 0; index < 6; index++) {
- writer.WriteBit(true);
- std::span<const u8> matrix{context.weight_scale};
- writer.WriteScalingList(matrix, index * 16, 16);
- }
-
- if (context.h264_parameter_set.transform_8x8_mode_flag) {
- for (s32 index = 0; index < 2; index++) {
- writer.WriteBit(true);
- std::span<const u8> matrix{context.weight_scale_8x8};
- writer.WriteScalingList(matrix, index * 64, 64);
- }
- }
-
- s32 chroma_qp_index_offset2 =
- static_cast<s32>(context.h264_parameter_set.second_chroma_qp_index_offset.Value());
-
- writer.WriteSe(chroma_qp_index_offset2);
-
- writer.End();
-
- const auto& encoded_header = writer.GetByteArray();
- frame.resize(encoded_header.size() + context.stream_len);
- std::memcpy(frame.data(), encoded_header.data(), encoded_header.size());
-
- gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset,
- frame.data() + encoded_header.size(), context.stream_len);
-
- return frame;
-}
-
-H264BitWriter::H264BitWriter() = default;
-
-H264BitWriter::~H264BitWriter() = default;
-
-void H264BitWriter::WriteU(s32 value, s32 value_sz) {
- WriteBits(value, value_sz);
-}
-
-void H264BitWriter::WriteSe(s32 value) {
- WriteExpGolombCodedInt(value);
-}
-
-void H264BitWriter::WriteUe(u32 value) {
- WriteExpGolombCodedUInt(value);
-}
-
-void H264BitWriter::End() {
- WriteBit(true);
- Flush();
-}
-
-void H264BitWriter::WriteBit(bool state) {
- WriteBits(state ? 1 : 0, 1);
-}
-
-void H264BitWriter::WriteScalingList(std::span<const u8> list, s32 start, s32 count) {
- std::vector<u8> scan(count);
- if (count == 16) {
- std::memcpy(scan.data(), zig_zag_scan.data(), scan.size());
- } else {
- std::memcpy(scan.data(), zig_zag_direct.data(), scan.size());
- }
- u8 last_scale = 8;
-
- for (s32 index = 0; index < count; index++) {
- const u8 value = list[start + scan[index]];
- const s32 delta_scale = static_cast<s32>(value - last_scale);
-
- WriteSe(delta_scale);
-
- last_scale = value;
- }
-}
-
-std::vector<u8>& H264BitWriter::GetByteArray() {
- return byte_array;
-}
-
-const std::vector<u8>& H264BitWriter::GetByteArray() const {
- return byte_array;
-}
-
-void H264BitWriter::WriteBits(s32 value, s32 bit_count) {
- s32 value_pos = 0;
-
- s32 remaining = bit_count;
-
- while (remaining > 0) {
- s32 copy_size = remaining;
-
- const s32 free_bits = GetFreeBufferBits();
-
- if (copy_size > free_bits) {
- copy_size = free_bits;
- }
-
- const s32 mask = (1 << copy_size) - 1;
-
- const s32 src_shift = (bit_count - value_pos) - copy_size;
- const s32 dst_shift = (buffer_size - buffer_pos) - copy_size;
-
- buffer |= ((value >> src_shift) & mask) << dst_shift;
-
- value_pos += copy_size;
- buffer_pos += copy_size;
- remaining -= copy_size;
- }
-}
-
-void H264BitWriter::WriteExpGolombCodedInt(s32 value) {
- const s32 sign = value <= 0 ? 0 : 1;
- if (value < 0) {
- value = -value;
- }
- value = (value << 1) - sign;
- WriteExpGolombCodedUInt(value);
-}
-
-void H264BitWriter::WriteExpGolombCodedUInt(u32 value) {
- const s32 size = 32 - std::countl_zero(value + 1);
- WriteBits(1, size);
-
- value -= (1U << (size - 1)) - 1;
- WriteBits(static_cast<s32>(value), size - 1);
-}
-
-s32 H264BitWriter::GetFreeBufferBits() {
- if (buffer_pos == buffer_size) {
- Flush();
- }
-
- return buffer_size - buffer_pos;
-}
-
-void H264BitWriter::Flush() {
- if (buffer_pos == 0) {
- return;
- }
- byte_array.push_back(static_cast<u8>(buffer));
-
- buffer = 0;
- buffer_pos = 0;
-}
-} // namespace Tegra::Decoder
diff --git a/src/video_core/command_classes/codecs/h264.h b/src/video_core/command_classes/codecs/h264.h
deleted file mode 100644
index 1899d8e7f..000000000
--- a/src/video_core/command_classes/codecs/h264.h
+++ /dev/null
@@ -1,190 +0,0 @@
-// MIT License
-//
-// Copyright (c) Ryujinx Team and Contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
-// associated documentation files (the "Software"), to deal in the Software without restriction,
-// including without limitation the rights to use, copy, modify, merge, publish, distribute,
-// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all copies or
-// substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
-// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-#pragma once
-
-#include <span>
-#include <vector>
-#include "common/bit_field.h"
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "video_core/command_classes/nvdec_common.h"
-
-namespace Tegra {
-class GPU;
-namespace Decoder {
-
-class H264BitWriter {
-public:
- H264BitWriter();
- ~H264BitWriter();
-
- /// The following Write methods are based on clause 9.1 in the H.264 specification.
- /// WriteSe and WriteUe write in the Exp-Golomb-coded syntax
- void WriteU(s32 value, s32 value_sz);
- void WriteSe(s32 value);
- void WriteUe(u32 value);
-
- /// Finalize the bitstream
- void End();
-
- /// append a bit to the stream, equivalent value to the state parameter
- void WriteBit(bool state);
-
- /// Based on section 7.3.2.1.1.1 and Table 7-4 in the H.264 specification
- /// Writes the scaling matrices of the sream
- void WriteScalingList(std::span<const u8> list, s32 start, s32 count);
-
- /// Return the bitstream as a vector.
- [[nodiscard]] std::vector<u8>& GetByteArray();
- [[nodiscard]] const std::vector<u8>& GetByteArray() const;
-
-private:
- void WriteBits(s32 value, s32 bit_count);
- void WriteExpGolombCodedInt(s32 value);
- void WriteExpGolombCodedUInt(u32 value);
- [[nodiscard]] s32 GetFreeBufferBits();
- void Flush();
-
- s32 buffer_size{8};
-
- s32 buffer{};
- s32 buffer_pos{};
- std::vector<u8> byte_array;
-};
-
-class H264 {
-public:
- explicit H264(GPU& gpu);
- ~H264();
-
- /// Compose the H264 frame for FFmpeg decoding
- [[nodiscard]] const std::vector<u8>& ComposeFrame(const NvdecCommon::NvdecRegisters& state,
- bool is_first_frame = false);
-
-private:
- std::vector<u8> frame;
- GPU& gpu;
-
- struct H264ParameterSet {
- s32 log2_max_pic_order_cnt_lsb_minus4; ///< 0x00
- s32 delta_pic_order_always_zero_flag; ///< 0x04
- s32 frame_mbs_only_flag; ///< 0x08
- u32 pic_width_in_mbs; ///< 0x0C
- u32 frame_height_in_map_units; ///< 0x10
- union { ///< 0x14
- BitField<0, 2, u32> tile_format;
- BitField<2, 3, u32> gob_height;
- };
- u32 entropy_coding_mode_flag; ///< 0x18
- s32 pic_order_present_flag; ///< 0x1C
- s32 num_refidx_l0_default_active; ///< 0x20
- s32 num_refidx_l1_default_active; ///< 0x24
- s32 deblocking_filter_control_present_flag; ///< 0x28
- s32 redundant_pic_cnt_present_flag; ///< 0x2C
- u32 transform_8x8_mode_flag; ///< 0x30
- u32 pitch_luma; ///< 0x34
- u32 pitch_chroma; ///< 0x38
- u32 luma_top_offset; ///< 0x3C
- u32 luma_bot_offset; ///< 0x40
- u32 luma_frame_offset; ///< 0x44
- u32 chroma_top_offset; ///< 0x48
- u32 chroma_bot_offset; ///< 0x4C
- u32 chroma_frame_offset; ///< 0x50
- u32 hist_buffer_size; ///< 0x54
- union { ///< 0x58
- union {
- BitField<0, 1, u64> mbaff_frame;
- BitField<1, 1, u64> direct_8x8_inference;
- BitField<2, 1, u64> weighted_pred;
- BitField<3, 1, u64> constrained_intra_pred;
- BitField<4, 1, u64> ref_pic;
- BitField<5, 1, u64> field_pic;
- BitField<6, 1, u64> bottom_field;
- BitField<7, 1, u64> second_field;
- } flags;
- BitField<8, 4, u64> log2_max_frame_num_minus4;
- BitField<12, 2, u64> chroma_format_idc;
- BitField<14, 2, u64> pic_order_cnt_type;
- BitField<16, 6, s64> pic_init_qp_minus26;
- BitField<22, 5, s64> chroma_qp_index_offset;
- BitField<27, 5, s64> second_chroma_qp_index_offset;
- BitField<32, 2, u64> weighted_bipred_idc;
- BitField<34, 7, u64> curr_pic_idx;
- BitField<41, 5, u64> curr_col_idx;
- BitField<46, 16, u64> frame_number;
- BitField<62, 1, u64> frame_surfaces;
- BitField<63, 1, u64> output_memory_layout;
- };
- };
- static_assert(sizeof(H264ParameterSet) == 0x60, "H264ParameterSet is an invalid size");
-
- struct H264DecoderContext {
- INSERT_PADDING_WORDS_NOINIT(18); ///< 0x0000
- u32 stream_len; ///< 0x0048
- INSERT_PADDING_WORDS_NOINIT(3); ///< 0x004C
- H264ParameterSet h264_parameter_set; ///< 0x0058
- INSERT_PADDING_WORDS_NOINIT(66); ///< 0x00B8
- std::array<u8, 0x60> weight_scale; ///< 0x01C0
- std::array<u8, 0x80> weight_scale_8x8; ///< 0x0220
- };
- static_assert(sizeof(H264DecoderContext) == 0x2A0, "H264DecoderContext is an invalid size");
-
-#define ASSERT_POSITION(field_name, position) \
- static_assert(offsetof(H264ParameterSet, field_name) == position, \
- "Field " #field_name " has invalid position")
-
- ASSERT_POSITION(log2_max_pic_order_cnt_lsb_minus4, 0x00);
- ASSERT_POSITION(delta_pic_order_always_zero_flag, 0x04);
- ASSERT_POSITION(frame_mbs_only_flag, 0x08);
- ASSERT_POSITION(pic_width_in_mbs, 0x0C);
- ASSERT_POSITION(frame_height_in_map_units, 0x10);
- ASSERT_POSITION(tile_format, 0x14);
- ASSERT_POSITION(entropy_coding_mode_flag, 0x18);
- ASSERT_POSITION(pic_order_present_flag, 0x1C);
- ASSERT_POSITION(num_refidx_l0_default_active, 0x20);
- ASSERT_POSITION(num_refidx_l1_default_active, 0x24);
- ASSERT_POSITION(deblocking_filter_control_present_flag, 0x28);
- ASSERT_POSITION(redundant_pic_cnt_present_flag, 0x2C);
- ASSERT_POSITION(transform_8x8_mode_flag, 0x30);
- ASSERT_POSITION(pitch_luma, 0x34);
- ASSERT_POSITION(pitch_chroma, 0x38);
- ASSERT_POSITION(luma_top_offset, 0x3C);
- ASSERT_POSITION(luma_bot_offset, 0x40);
- ASSERT_POSITION(luma_frame_offset, 0x44);
- ASSERT_POSITION(chroma_top_offset, 0x48);
- ASSERT_POSITION(chroma_bot_offset, 0x4C);
- ASSERT_POSITION(chroma_frame_offset, 0x50);
- ASSERT_POSITION(hist_buffer_size, 0x54);
- ASSERT_POSITION(flags, 0x58);
-#undef ASSERT_POSITION
-
-#define ASSERT_POSITION(field_name, position) \
- static_assert(offsetof(H264DecoderContext, field_name) == position, \
- "Field " #field_name " has invalid position")
-
- ASSERT_POSITION(stream_len, 0x48);
- ASSERT_POSITION(h264_parameter_set, 0x58);
- ASSERT_POSITION(weight_scale, 0x1C0);
-#undef ASSERT_POSITION
-};
-
-} // namespace Decoder
-} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/vp8.cpp b/src/video_core/command_classes/codecs/vp8.cpp
deleted file mode 100644
index 32ad0ec16..000000000
--- a/src/video_core/command_classes/codecs/vp8.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <array>
-#include <vector>
-
-#include "video_core/command_classes/codecs/vp8.h"
-#include "video_core/gpu.h"
-#include "video_core/memory_manager.h"
-
-namespace Tegra::Decoder {
-VP8::VP8(GPU& gpu_) : gpu(gpu_) {}
-
-VP8::~VP8() = default;
-
-const std::vector<u8>& VP8::ComposeFrame(const NvdecCommon::NvdecRegisters& state) {
- VP8PictureInfo info;
- gpu.MemoryManager().ReadBlock(state.picture_info_offset, &info, sizeof(VP8PictureInfo));
-
- const bool is_key_frame = info.key_frame == 1u;
- const auto bitstream_size = static_cast<size_t>(info.vld_buffer_size);
- const size_t header_size = is_key_frame ? 10u : 3u;
- frame.resize(header_size + bitstream_size);
-
- // Based on page 30 of the VP8 specification.
- // https://datatracker.ietf.org/doc/rfc6386/
- frame[0] = is_key_frame ? 0u : 1u; // 1-bit frame type (0: keyframe, 1: interframes).
- frame[0] |= static_cast<u8>((info.version & 7u) << 1u); // 3-bit version number
- frame[0] |= static_cast<u8>(1u << 4u); // 1-bit show_frame flag
-
- // The next 19-bits are the first partition size
- frame[0] |= static_cast<u8>((info.first_part_size & 7u) << 5u);
- frame[1] = static_cast<u8>((info.first_part_size & 0x7f8u) >> 3u);
- frame[2] = static_cast<u8>((info.first_part_size & 0x7f800u) >> 11u);
-
- if (is_key_frame) {
- frame[3] = 0x9du;
- frame[4] = 0x01u;
- frame[5] = 0x2au;
- // TODO(ameerj): Horizontal/Vertical Scale
- // 16 bits: (2 bits Horizontal Scale << 14) | Width (14 bits)
- frame[6] = static_cast<u8>(info.frame_width & 0xff);
- frame[7] = static_cast<u8>(((info.frame_width >> 8) & 0x3f));
- // 16 bits:(2 bits Vertical Scale << 14) | Height (14 bits)
- frame[8] = static_cast<u8>(info.frame_height & 0xff);
- frame[9] = static_cast<u8>(((info.frame_height >> 8) & 0x3f));
- }
- const u64 bitstream_offset = state.frame_bitstream_offset;
- gpu.MemoryManager().ReadBlock(bitstream_offset, frame.data() + header_size, bitstream_size);
-
- return frame;
-}
-
-} // namespace Tegra::Decoder
diff --git a/src/video_core/command_classes/codecs/vp8.h b/src/video_core/command_classes/codecs/vp8.h
deleted file mode 100644
index 41fc7b403..000000000
--- a/src/video_core/command_classes/codecs/vp8.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <vector>
-
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "video_core/command_classes/nvdec_common.h"
-
-namespace Tegra {
-class GPU;
-namespace Decoder {
-
-class VP8 {
-public:
- explicit VP8(GPU& gpu);
- ~VP8();
-
- /// Compose the VP8 frame for FFmpeg decoding
- [[nodiscard]] const std::vector<u8>& ComposeFrame(const NvdecCommon::NvdecRegisters& state);
-
-private:
- std::vector<u8> frame;
- GPU& gpu;
-
- struct VP8PictureInfo {
- INSERT_PADDING_WORDS_NOINIT(14);
- u16 frame_width; // actual frame width
- u16 frame_height; // actual frame height
- u8 key_frame;
- u8 version;
- union {
- u8 raw;
- BitField<0, 2, u8> tile_format;
- BitField<2, 3, u8> gob_height;
- BitField<5, 3, u8> reserverd_surface_format;
- };
- u8 error_conceal_on; // 1: error conceal on; 0: off
- u32 first_part_size; // the size of first partition(frame header and mb header partition)
- u32 hist_buffer_size; // in units of 256
- u32 vld_buffer_size; // in units of 1
- // Current frame buffers
- std::array<u32, 2> frame_stride; // [y_c]
- u32 luma_top_offset; // offset of luma top field in units of 256
- u32 luma_bot_offset; // offset of luma bottom field in units of 256
- u32 luma_frame_offset; // offset of luma frame in units of 256
- u32 chroma_top_offset; // offset of chroma top field in units of 256
- u32 chroma_bot_offset; // offset of chroma bottom field in units of 256
- u32 chroma_frame_offset; // offset of chroma frame in units of 256
-
- INSERT_PADDING_BYTES_NOINIT(0x1c); // NvdecDisplayParams
-
- // Decode picture buffer related
- s8 current_output_memory_layout;
- // output NV12/NV24 setting. index 0: golden; 1: altref; 2: last
- std::array<s8, 3> output_memory_layout;
-
- u8 segmentation_feature_data_update;
- INSERT_PADDING_BYTES_NOINIT(3);
-
- // ucode return result
- u32 result_value;
- std::array<u32, 8> partition_offset;
- INSERT_PADDING_WORDS_NOINIT(3);
- };
- static_assert(sizeof(VP8PictureInfo) == 0xc0, "PictureInfo is an invalid size");
-};
-
-} // namespace Decoder
-} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/vp9.cpp b/src/video_core/command_classes/codecs/vp9.cpp
deleted file mode 100644
index 2c00181fa..000000000
--- a/src/video_core/command_classes/codecs/vp9.cpp
+++ /dev/null
@@ -1,947 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm> // for std::copy
-#include <numeric>
-#include "common/assert.h"
-#include "video_core/command_classes/codecs/vp9.h"
-#include "video_core/gpu.h"
-#include "video_core/memory_manager.h"
-
-namespace Tegra::Decoder {
-namespace {
-constexpr u32 diff_update_probability = 252;
-constexpr u32 frame_sync_code = 0x498342;
-
-// Default compressed header probabilities once frame context resets
-constexpr Vp9EntropyProbs default_probs{
- .y_mode_prob{
- 65, 32, 18, 144, 162, 194, 41, 51, 98, 132, 68, 18, 165, 217, 196, 45, 40, 78,
- 173, 80, 19, 176, 240, 193, 64, 35, 46, 221, 135, 38, 194, 248, 121, 96, 85, 29,
- },
- .partition_prob{
- 199, 122, 141, 0, 147, 63, 159, 0, 148, 133, 118, 0, 121, 104, 114, 0,
- 174, 73, 87, 0, 92, 41, 83, 0, 82, 99, 50, 0, 53, 39, 39, 0,
- 177, 58, 59, 0, 68, 26, 63, 0, 52, 79, 25, 0, 17, 14, 12, 0,
- 222, 34, 30, 0, 72, 16, 44, 0, 58, 32, 12, 0, 10, 7, 6, 0,
- },
- .coef_probs{
- 195, 29, 183, 84, 49, 136, 8, 42, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 31, 107, 169, 35, 99, 159, 17, 82, 140, 8, 66, 114, 2, 44, 76, 1, 19, 32,
- 40, 132, 201, 29, 114, 187, 13, 91, 157, 7, 75, 127, 3, 58, 95, 1, 28, 47,
- 69, 142, 221, 42, 122, 201, 15, 91, 159, 6, 67, 121, 1, 42, 77, 1, 17, 31,
- 102, 148, 228, 67, 117, 204, 17, 82, 154, 6, 59, 114, 2, 39, 75, 1, 15, 29,
- 156, 57, 233, 119, 57, 212, 58, 48, 163, 29, 40, 124, 12, 30, 81, 3, 12, 31,
- 191, 107, 226, 124, 117, 204, 25, 99, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 29, 148, 210, 37, 126, 194, 8, 93, 157, 2, 68, 118, 1, 39, 69, 1, 17, 33,
- 41, 151, 213, 27, 123, 193, 3, 82, 144, 1, 58, 105, 1, 32, 60, 1, 13, 26,
- 59, 159, 220, 23, 126, 198, 4, 88, 151, 1, 66, 114, 1, 38, 71, 1, 18, 34,
- 114, 136, 232, 51, 114, 207, 11, 83, 155, 3, 56, 105, 1, 33, 65, 1, 17, 34,
- 149, 65, 234, 121, 57, 215, 61, 49, 166, 28, 36, 114, 12, 25, 76, 3, 16, 42,
- 214, 49, 220, 132, 63, 188, 42, 65, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 85, 137, 221, 104, 131, 216, 49, 111, 192, 21, 87, 155, 2, 49, 87, 1, 16, 28,
- 89, 163, 230, 90, 137, 220, 29, 100, 183, 10, 70, 135, 2, 42, 81, 1, 17, 33,
- 108, 167, 237, 55, 133, 222, 15, 97, 179, 4, 72, 135, 1, 45, 85, 1, 19, 38,
- 124, 146, 240, 66, 124, 224, 17, 88, 175, 4, 58, 122, 1, 36, 75, 1, 18, 37,
- 141, 79, 241, 126, 70, 227, 66, 58, 182, 30, 44, 136, 12, 34, 96, 2, 20, 47,
- 229, 99, 249, 143, 111, 235, 46, 109, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 82, 158, 236, 94, 146, 224, 25, 117, 191, 9, 87, 149, 3, 56, 99, 1, 33, 57,
- 83, 167, 237, 68, 145, 222, 10, 103, 177, 2, 72, 131, 1, 41, 79, 1, 20, 39,
- 99, 167, 239, 47, 141, 224, 10, 104, 178, 2, 73, 133, 1, 44, 85, 1, 22, 47,
- 127, 145, 243, 71, 129, 228, 17, 93, 177, 3, 61, 124, 1, 41, 84, 1, 21, 52,
- 157, 78, 244, 140, 72, 231, 69, 58, 184, 31, 44, 137, 14, 38, 105, 8, 23, 61,
- 125, 34, 187, 52, 41, 133, 6, 31, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 37, 109, 153, 51, 102, 147, 23, 87, 128, 8, 67, 101, 1, 41, 63, 1, 19, 29,
- 31, 154, 185, 17, 127, 175, 6, 96, 145, 2, 73, 114, 1, 51, 82, 1, 28, 45,
- 23, 163, 200, 10, 131, 185, 2, 93, 148, 1, 67, 111, 1, 41, 69, 1, 14, 24,
- 29, 176, 217, 12, 145, 201, 3, 101, 156, 1, 69, 111, 1, 39, 63, 1, 14, 23,
- 57, 192, 233, 25, 154, 215, 6, 109, 167, 3, 78, 118, 1, 48, 69, 1, 21, 29,
- 202, 105, 245, 108, 106, 216, 18, 90, 144, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 33, 172, 219, 64, 149, 206, 14, 117, 177, 5, 90, 141, 2, 61, 95, 1, 37, 57,
- 33, 179, 220, 11, 140, 198, 1, 89, 148, 1, 60, 104, 1, 33, 57, 1, 12, 21,
- 30, 181, 221, 8, 141, 198, 1, 87, 145, 1, 58, 100, 1, 31, 55, 1, 12, 20,
- 32, 186, 224, 7, 142, 198, 1, 86, 143, 1, 58, 100, 1, 31, 55, 1, 12, 22,
- 57, 192, 227, 20, 143, 204, 3, 96, 154, 1, 68, 112, 1, 42, 69, 1, 19, 32,
- 212, 35, 215, 113, 47, 169, 29, 48, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 74, 129, 203, 106, 120, 203, 49, 107, 178, 19, 84, 144, 4, 50, 84, 1, 15, 25,
- 71, 172, 217, 44, 141, 209, 15, 102, 173, 6, 76, 133, 2, 51, 89, 1, 24, 42,
- 64, 185, 231, 31, 148, 216, 8, 103, 175, 3, 74, 131, 1, 46, 81, 1, 18, 30,
- 65, 196, 235, 25, 157, 221, 5, 105, 174, 1, 67, 120, 1, 38, 69, 1, 15, 30,
- 65, 204, 238, 30, 156, 224, 7, 107, 177, 2, 70, 124, 1, 42, 73, 1, 18, 34,
- 225, 86, 251, 144, 104, 235, 42, 99, 181, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 85, 175, 239, 112, 165, 229, 29, 136, 200, 12, 103, 162, 6, 77, 123, 2, 53, 84,
- 75, 183, 239, 30, 155, 221, 3, 106, 171, 1, 74, 128, 1, 44, 76, 1, 17, 28,
- 73, 185, 240, 27, 159, 222, 2, 107, 172, 1, 75, 127, 1, 42, 73, 1, 17, 29,
- 62, 190, 238, 21, 159, 222, 2, 107, 172, 1, 72, 122, 1, 40, 71, 1, 18, 32,
- 61, 199, 240, 27, 161, 226, 4, 113, 180, 1, 76, 129, 1, 46, 80, 1, 23, 41,
- 7, 27, 153, 5, 30, 95, 1, 16, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 50, 75, 127, 57, 75, 124, 27, 67, 108, 10, 54, 86, 1, 33, 52, 1, 12, 18,
- 43, 125, 151, 26, 108, 148, 7, 83, 122, 2, 59, 89, 1, 38, 60, 1, 17, 27,
- 23, 144, 163, 13, 112, 154, 2, 75, 117, 1, 50, 81, 1, 31, 51, 1, 14, 23,
- 18, 162, 185, 6, 123, 171, 1, 78, 125, 1, 51, 86, 1, 31, 54, 1, 14, 23,
- 15, 199, 227, 3, 150, 204, 1, 91, 146, 1, 55, 95, 1, 30, 53, 1, 11, 20,
- 19, 55, 240, 19, 59, 196, 3, 52, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 41, 166, 207, 104, 153, 199, 31, 123, 181, 14, 101, 152, 5, 72, 106, 1, 36, 52,
- 35, 176, 211, 12, 131, 190, 2, 88, 144, 1, 60, 101, 1, 36, 60, 1, 16, 28,
- 28, 183, 213, 8, 134, 191, 1, 86, 142, 1, 56, 96, 1, 30, 53, 1, 12, 20,
- 20, 190, 215, 4, 135, 192, 1, 84, 139, 1, 53, 91, 1, 28, 49, 1, 11, 20,
- 13, 196, 216, 2, 137, 192, 1, 86, 143, 1, 57, 99, 1, 32, 56, 1, 13, 24,
- 211, 29, 217, 96, 47, 156, 22, 43, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 78, 120, 193, 111, 116, 186, 46, 102, 164, 15, 80, 128, 2, 49, 76, 1, 18, 28,
- 71, 161, 203, 42, 132, 192, 10, 98, 150, 3, 69, 109, 1, 44, 70, 1, 18, 29,
- 57, 186, 211, 30, 140, 196, 4, 93, 146, 1, 62, 102, 1, 38, 65, 1, 16, 27,
- 47, 199, 217, 14, 145, 196, 1, 88, 142, 1, 57, 98, 1, 36, 62, 1, 15, 26,
- 26, 219, 229, 5, 155, 207, 1, 94, 151, 1, 60, 104, 1, 36, 62, 1, 16, 28,
- 233, 29, 248, 146, 47, 220, 43, 52, 140, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 100, 163, 232, 179, 161, 222, 63, 142, 204, 37, 113, 174, 26, 89, 137, 18, 68, 97,
- 85, 181, 230, 32, 146, 209, 7, 100, 164, 3, 71, 121, 1, 45, 77, 1, 18, 30,
- 65, 187, 230, 20, 148, 207, 2, 97, 159, 1, 68, 116, 1, 40, 70, 1, 14, 29,
- 40, 194, 227, 8, 147, 204, 1, 94, 155, 1, 65, 112, 1, 39, 66, 1, 14, 26,
- 16, 208, 228, 3, 151, 207, 1, 98, 160, 1, 67, 117, 1, 41, 74, 1, 17, 31,
- 17, 38, 140, 7, 34, 80, 1, 17, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 37, 75, 128, 41, 76, 128, 26, 66, 116, 12, 52, 94, 2, 32, 55, 1, 10, 16,
- 50, 127, 154, 37, 109, 152, 16, 82, 121, 5, 59, 85, 1, 35, 54, 1, 13, 20,
- 40, 142, 167, 17, 110, 157, 2, 71, 112, 1, 44, 72, 1, 27, 45, 1, 11, 17,
- 30, 175, 188, 9, 124, 169, 1, 74, 116, 1, 48, 78, 1, 30, 49, 1, 11, 18,
- 10, 222, 223, 2, 150, 194, 1, 83, 128, 1, 48, 79, 1, 27, 45, 1, 11, 17,
- 36, 41, 235, 29, 36, 193, 10, 27, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 85, 165, 222, 177, 162, 215, 110, 135, 195, 57, 113, 168, 23, 83, 120, 10, 49, 61,
- 85, 190, 223, 36, 139, 200, 5, 90, 146, 1, 60, 103, 1, 38, 65, 1, 18, 30,
- 72, 202, 223, 23, 141, 199, 2, 86, 140, 1, 56, 97, 1, 36, 61, 1, 16, 27,
- 55, 218, 225, 13, 145, 200, 1, 86, 141, 1, 57, 99, 1, 35, 61, 1, 13, 22,
- 15, 235, 212, 1, 132, 184, 1, 84, 139, 1, 57, 97, 1, 34, 56, 1, 14, 23,
- 181, 21, 201, 61, 37, 123, 10, 38, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 47, 106, 172, 95, 104, 173, 42, 93, 159, 18, 77, 131, 4, 50, 81, 1, 17, 23,
- 62, 147, 199, 44, 130, 189, 28, 102, 154, 18, 75, 115, 2, 44, 65, 1, 12, 19,
- 55, 153, 210, 24, 130, 194, 3, 93, 146, 1, 61, 97, 1, 31, 50, 1, 10, 16,
- 49, 186, 223, 17, 148, 204, 1, 96, 142, 1, 53, 83, 1, 26, 44, 1, 11, 17,
- 13, 217, 212, 2, 136, 180, 1, 78, 124, 1, 50, 83, 1, 29, 49, 1, 14, 23,
- 197, 13, 247, 82, 17, 222, 25, 17, 162, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 126, 186, 247, 234, 191, 243, 176, 177, 234, 104, 158, 220, 66, 128, 186, 55, 90, 137,
- 111, 197, 242, 46, 158, 219, 9, 104, 171, 2, 65, 125, 1, 44, 80, 1, 17, 91,
- 104, 208, 245, 39, 168, 224, 3, 109, 162, 1, 79, 124, 1, 50, 102, 1, 43, 102,
- 84, 220, 246, 31, 177, 231, 2, 115, 180, 1, 79, 134, 1, 55, 77, 1, 60, 79,
- 43, 243, 240, 8, 180, 217, 1, 115, 166, 1, 84, 121, 1, 51, 67, 1, 16, 6,
- },
- .switchable_interp_prob{235, 162, 36, 255, 34, 3, 149, 144},
- .inter_mode_prob{
- 2, 173, 34, 0, 7, 145, 85, 0, 7, 166, 63, 0, 7, 94,
- 66, 0, 8, 64, 46, 0, 17, 81, 31, 0, 25, 29, 30, 0,
- },
- .intra_inter_prob{9, 102, 187, 225},
- .comp_inter_prob{9, 102, 187, 225, 0},
- .single_ref_prob{33, 16, 77, 74, 142, 142, 172, 170, 238, 247},
- .comp_ref_prob{50, 126, 123, 221, 226},
- .tx_32x32_prob{3, 136, 37, 5, 52, 13},
- .tx_16x16_prob{20, 152, 15, 101},
- .tx_8x8_prob{100, 66},
- .skip_probs{192, 128, 64},
- .joints{32, 64, 96},
- .sign{128, 128},
- .classes{
- 224, 144, 192, 168, 192, 176, 192, 198, 198, 245,
- 216, 128, 176, 160, 176, 176, 192, 198, 198, 208,
- },
- .class_0{216, 208},
- .prob_bits{
- 136, 140, 148, 160, 176, 192, 224, 234, 234, 240,
- 136, 140, 148, 160, 176, 192, 224, 234, 234, 240,
- },
- .class_0_fr{128, 128, 64, 96, 112, 64, 128, 128, 64, 96, 112, 64},
- .fr{64, 96, 64, 64, 96, 64},
- .class_0_hp{160, 160},
- .high_precision{128, 128},
-};
-
-constexpr std::array<s32, 256> norm_lut{
- 0, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-};
-
-constexpr std::array<s32, 254> map_lut{
- 20, 21, 22, 23, 24, 25, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
- 1, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 2, 50, 51, 52, 53, 54,
- 55, 56, 57, 58, 59, 60, 61, 3, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
- 73, 4, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 5, 86, 87, 88, 89,
- 90, 91, 92, 93, 94, 95, 96, 97, 6, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
- 108, 109, 7, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 8, 122, 123, 124,
- 125, 126, 127, 128, 129, 130, 131, 132, 133, 9, 134, 135, 136, 137, 138, 139, 140, 141, 142,
- 143, 144, 145, 10, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 11, 158, 159,
- 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 12, 170, 171, 172, 173, 174, 175, 176, 177,
- 178, 179, 180, 181, 13, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 14, 194,
- 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 15, 206, 207, 208, 209, 210, 211, 212,
- 213, 214, 215, 216, 217, 16, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 17,
- 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 18, 242, 243, 244, 245, 246, 247,
- 248, 249, 250, 251, 252, 253, 19,
-};
-
-// 6.2.14 Tile size calculation
-
-[[nodiscard]] s32 CalcMinLog2TileCols(s32 frame_width) {
- const s32 sb64_cols = (frame_width + 63) / 64;
- s32 min_log2 = 0;
-
- while ((64 << min_log2) < sb64_cols) {
- min_log2++;
- }
-
- return min_log2;
-}
-
-[[nodiscard]] s32 CalcMaxLog2TileCols(s32 frame_width) {
- const s32 sb64_cols = (frame_width + 63) / 64;
- s32 max_log2 = 1;
-
- while ((sb64_cols >> max_log2) >= 4) {
- max_log2++;
- }
-
- return max_log2 - 1;
-}
-
-// Recenters probability. Based on section 6.3.6 of VP9 Specification
-[[nodiscard]] s32 RecenterNonNeg(s32 new_prob, s32 old_prob) {
- if (new_prob > old_prob * 2) {
- return new_prob;
- }
-
- if (new_prob >= old_prob) {
- return (new_prob - old_prob) * 2;
- }
-
- return (old_prob - new_prob) * 2 - 1;
-}
-
-// Adjusts old_prob depending on new_prob. Based on section 6.3.5 of VP9 Specification
-[[nodiscard]] s32 RemapProbability(s32 new_prob, s32 old_prob) {
- new_prob--;
- old_prob--;
-
- std::size_t index{};
-
- if (old_prob * 2 <= 0xff) {
- index = static_cast<std::size_t>(std::max(0, RecenterNonNeg(new_prob, old_prob) - 1));
- } else {
- index = static_cast<std::size_t>(
- std::max(0, RecenterNonNeg(0xff - 1 - new_prob, 0xff - 1 - old_prob) - 1));
- }
-
- return map_lut[index];
-}
-} // Anonymous namespace
-
-VP9::VP9(GPU& gpu_) : gpu{gpu_} {}
-
-VP9::~VP9() = default;
-
-void VP9::WriteProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) {
- const bool update = new_prob != old_prob;
-
- writer.Write(update, diff_update_probability);
-
- if (update) {
- WriteProbabilityDelta(writer, new_prob, old_prob);
- }
-}
-template <typename T, std::size_t N>
-void VP9::WriteProbabilityUpdate(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
- const std::array<T, N>& old_prob) {
- for (std::size_t offset = 0; offset < new_prob.size(); ++offset) {
- WriteProbabilityUpdate(writer, new_prob[offset], old_prob[offset]);
- }
-}
-
-template <typename T, std::size_t N>
-void VP9::WriteProbabilityUpdateAligned4(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
- const std::array<T, N>& old_prob) {
- for (std::size_t offset = 0; offset < new_prob.size(); offset += 4) {
- WriteProbabilityUpdate(writer, new_prob[offset + 0], old_prob[offset + 0]);
- WriteProbabilityUpdate(writer, new_prob[offset + 1], old_prob[offset + 1]);
- WriteProbabilityUpdate(writer, new_prob[offset + 2], old_prob[offset + 2]);
- }
-}
-
-void VP9::WriteProbabilityDelta(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) {
- const int delta = RemapProbability(new_prob, old_prob);
-
- EncodeTermSubExp(writer, delta);
-}
-
-void VP9::EncodeTermSubExp(VpxRangeEncoder& writer, s32 value) {
- if (WriteLessThan(writer, value, 16)) {
- writer.Write(value, 4);
- } else if (WriteLessThan(writer, value, 32)) {
- writer.Write(value - 16, 4);
- } else if (WriteLessThan(writer, value, 64)) {
- writer.Write(value - 32, 5);
- } else {
- value -= 64;
-
- constexpr s32 size = 8;
-
- const s32 mask = (1 << size) - 191;
-
- const s32 delta = value - mask;
-
- if (delta < 0) {
- writer.Write(value, size - 1);
- } else {
- writer.Write(delta / 2 + mask, size - 1);
- writer.Write(delta & 1, 1);
- }
- }
-}
-
-bool VP9::WriteLessThan(VpxRangeEncoder& writer, s32 value, s32 test) {
- const bool is_lt = value < test;
- writer.Write(!is_lt);
- return is_lt;
-}
-
-void VP9::WriteCoefProbabilityUpdate(VpxRangeEncoder& writer, s32 tx_mode,
- const std::array<u8, 1728>& new_prob,
- const std::array<u8, 1728>& old_prob) {
- constexpr u32 block_bytes = 2 * 2 * 6 * 6 * 3;
-
- const auto needs_update = [&](u32 base_index) {
- return !std::equal(new_prob.begin() + base_index,
- new_prob.begin() + base_index + block_bytes,
- old_prob.begin() + base_index);
- };
-
- for (u32 block_index = 0; block_index < 4; block_index++) {
- const u32 base_index = block_index * block_bytes;
- const bool update = needs_update(base_index);
- writer.Write(update);
-
- if (update) {
- u32 index = base_index;
- for (s32 i = 0; i < 2; i++) {
- for (s32 j = 0; j < 2; j++) {
- for (s32 k = 0; k < 6; k++) {
- for (s32 l = 0; l < 6; l++) {
- if (k != 0 || l < 3) {
- WriteProbabilityUpdate(writer, new_prob[index + 0],
- old_prob[index + 0]);
- WriteProbabilityUpdate(writer, new_prob[index + 1],
- old_prob[index + 1]);
- WriteProbabilityUpdate(writer, new_prob[index + 2],
- old_prob[index + 2]);
- }
- index += 3;
- }
- }
- }
- }
- }
- if (block_index == static_cast<u32>(tx_mode)) {
- break;
- }
- }
-}
-
-void VP9::WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) {
- const bool update = new_prob != old_prob;
- writer.Write(update, diff_update_probability);
-
- if (update) {
- writer.Write(new_prob >> 1, 7);
- }
-}
-
-Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state) {
- PictureInfo picture_info;
- gpu.MemoryManager().ReadBlock(state.picture_info_offset, &picture_info, sizeof(PictureInfo));
- Vp9PictureInfo vp9_info = picture_info.Convert();
-
- InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy);
-
- // surface_luma_offset[0:3] contains the address of the reference frame offsets in the following
- // order: last, golden, altref, current.
- std::copy(state.surface_luma_offset.begin(), state.surface_luma_offset.begin() + 4,
- vp9_info.frame_offsets.begin());
-
- return vp9_info;
-}
-
-void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
- EntropyProbs entropy;
- gpu.MemoryManager().ReadBlock(offset, &entropy, sizeof(EntropyProbs));
- entropy.Convert(dst);
-}
-
-Vp9FrameContainer VP9::GetCurrentFrame(const NvdecCommon::NvdecRegisters& state) {
- Vp9FrameContainer current_frame{};
- {
- gpu.SyncGuestHost();
- current_frame.info = GetVp9PictureInfo(state);
- current_frame.bit_stream.resize(current_frame.info.bitstream_size);
- gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, current_frame.bit_stream.data(),
- current_frame.info.bitstream_size);
- }
- if (!next_frame.bit_stream.empty()) {
- Vp9FrameContainer temp{
- .info = current_frame.info,
- .bit_stream = std::move(current_frame.bit_stream),
- };
- next_frame.info.show_frame = current_frame.info.last_frame_shown;
- current_frame.info = next_frame.info;
- current_frame.bit_stream = std::move(next_frame.bit_stream);
- next_frame = std::move(temp);
- } else {
- next_frame.info = current_frame.info;
- next_frame.bit_stream = current_frame.bit_stream;
- }
- return current_frame;
-}
-
-std::vector<u8> VP9::ComposeCompressedHeader() {
- VpxRangeEncoder writer{};
- const bool update_probs = !current_frame_info.is_key_frame && current_frame_info.show_frame;
- if (!current_frame_info.lossless) {
- if (static_cast<u32>(current_frame_info.transform_mode) >= 3) {
- writer.Write(3, 2);
- writer.Write(current_frame_info.transform_mode == 4);
- } else {
- writer.Write(current_frame_info.transform_mode, 2);
- }
- }
-
- if (current_frame_info.transform_mode == 4) {
- // tx_mode_probs() in the spec
- WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_8x8_prob,
- prev_frame_probs.tx_8x8_prob);
- WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_16x16_prob,
- prev_frame_probs.tx_16x16_prob);
- WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_32x32_prob,
- prev_frame_probs.tx_32x32_prob);
- if (update_probs) {
- prev_frame_probs.tx_8x8_prob = current_frame_info.entropy.tx_8x8_prob;
- prev_frame_probs.tx_16x16_prob = current_frame_info.entropy.tx_16x16_prob;
- prev_frame_probs.tx_32x32_prob = current_frame_info.entropy.tx_32x32_prob;
- }
- }
- // read_coef_probs() in the spec
- WriteCoefProbabilityUpdate(writer, current_frame_info.transform_mode,
- current_frame_info.entropy.coef_probs, prev_frame_probs.coef_probs);
- // read_skip_probs() in the spec
- WriteProbabilityUpdate(writer, current_frame_info.entropy.skip_probs,
- prev_frame_probs.skip_probs);
-
- if (update_probs) {
- prev_frame_probs.coef_probs = current_frame_info.entropy.coef_probs;
- prev_frame_probs.skip_probs = current_frame_info.entropy.skip_probs;
- }
-
- if (!current_frame_info.intra_only) {
- // read_inter_probs() in the spec
- WriteProbabilityUpdateAligned4(writer, current_frame_info.entropy.inter_mode_prob,
- prev_frame_probs.inter_mode_prob);
-
- if (current_frame_info.interp_filter == 4) {
- // read_interp_filter_probs() in the spec
- WriteProbabilityUpdate(writer, current_frame_info.entropy.switchable_interp_prob,
- prev_frame_probs.switchable_interp_prob);
- if (update_probs) {
- prev_frame_probs.switchable_interp_prob =
- current_frame_info.entropy.switchable_interp_prob;
- }
- }
-
- // read_is_inter_probs() in the spec
- WriteProbabilityUpdate(writer, current_frame_info.entropy.intra_inter_prob,
- prev_frame_probs.intra_inter_prob);
-
- // frame_reference_mode() in the spec
- if ((current_frame_info.ref_frame_sign_bias[1] & 1) !=
- (current_frame_info.ref_frame_sign_bias[2] & 1) ||
- (current_frame_info.ref_frame_sign_bias[1] & 1) !=
- (current_frame_info.ref_frame_sign_bias[3] & 1)) {
- if (current_frame_info.reference_mode >= 1) {
- writer.Write(1, 1);
- writer.Write(current_frame_info.reference_mode == 2);
- } else {
- writer.Write(0, 1);
- }
- }
-
- // frame_reference_mode_probs() in the spec
- if (current_frame_info.reference_mode == 2) {
- WriteProbabilityUpdate(writer, current_frame_info.entropy.comp_inter_prob,
- prev_frame_probs.comp_inter_prob);
- if (update_probs) {
- prev_frame_probs.comp_inter_prob = current_frame_info.entropy.comp_inter_prob;
- }
- }
-
- if (current_frame_info.reference_mode != 1) {
- WriteProbabilityUpdate(writer, current_frame_info.entropy.single_ref_prob,
- prev_frame_probs.single_ref_prob);
- if (update_probs) {
- prev_frame_probs.single_ref_prob = current_frame_info.entropy.single_ref_prob;
- }
- }
-
- if (current_frame_info.reference_mode != 0) {
- WriteProbabilityUpdate(writer, current_frame_info.entropy.comp_ref_prob,
- prev_frame_probs.comp_ref_prob);
- if (update_probs) {
- prev_frame_probs.comp_ref_prob = current_frame_info.entropy.comp_ref_prob;
- }
- }
-
- // read_y_mode_probs
- for (std::size_t index = 0; index < current_frame_info.entropy.y_mode_prob.size();
- ++index) {
- WriteProbabilityUpdate(writer, current_frame_info.entropy.y_mode_prob[index],
- prev_frame_probs.y_mode_prob[index]);
- }
-
- // read_partition_probs
- WriteProbabilityUpdateAligned4(writer, current_frame_info.entropy.partition_prob,
- prev_frame_probs.partition_prob);
-
- // mv_probs
- for (s32 i = 0; i < 3; i++) {
- WriteMvProbabilityUpdate(writer, current_frame_info.entropy.joints[i],
- prev_frame_probs.joints[i]);
- }
- if (update_probs) {
- prev_frame_probs.inter_mode_prob = current_frame_info.entropy.inter_mode_prob;
- prev_frame_probs.intra_inter_prob = current_frame_info.entropy.intra_inter_prob;
- prev_frame_probs.y_mode_prob = current_frame_info.entropy.y_mode_prob;
- prev_frame_probs.partition_prob = current_frame_info.entropy.partition_prob;
- prev_frame_probs.joints = current_frame_info.entropy.joints;
- }
-
- for (s32 i = 0; i < 2; i++) {
- WriteMvProbabilityUpdate(writer, current_frame_info.entropy.sign[i],
- prev_frame_probs.sign[i]);
- for (s32 j = 0; j < 10; j++) {
- const int index = i * 10 + j;
- WriteMvProbabilityUpdate(writer, current_frame_info.entropy.classes[index],
- prev_frame_probs.classes[index]);
- }
- WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0[i],
- prev_frame_probs.class_0[i]);
-
- for (s32 j = 0; j < 10; j++) {
- const int index = i * 10 + j;
- WriteMvProbabilityUpdate(writer, current_frame_info.entropy.prob_bits[index],
- prev_frame_probs.prob_bits[index]);
- }
- }
-
- for (s32 i = 0; i < 2; i++) {
- for (s32 j = 0; j < 2; j++) {
- for (s32 k = 0; k < 3; k++) {
- const int index = i * 2 * 3 + j * 3 + k;
- WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0_fr[index],
- prev_frame_probs.class_0_fr[index]);
- }
- }
-
- for (s32 j = 0; j < 3; j++) {
- const int index = i * 3 + j;
- WriteMvProbabilityUpdate(writer, current_frame_info.entropy.fr[index],
- prev_frame_probs.fr[index]);
- }
- }
-
- if (current_frame_info.allow_high_precision_mv) {
- for (s32 index = 0; index < 2; index++) {
- WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0_hp[index],
- prev_frame_probs.class_0_hp[index]);
- WriteMvProbabilityUpdate(writer, current_frame_info.entropy.high_precision[index],
- prev_frame_probs.high_precision[index]);
- }
- }
-
- // save previous probs
- if (update_probs) {
- prev_frame_probs.sign = current_frame_info.entropy.sign;
- prev_frame_probs.classes = current_frame_info.entropy.classes;
- prev_frame_probs.class_0 = current_frame_info.entropy.class_0;
- prev_frame_probs.prob_bits = current_frame_info.entropy.prob_bits;
- prev_frame_probs.class_0_fr = current_frame_info.entropy.class_0_fr;
- prev_frame_probs.fr = current_frame_info.entropy.fr;
- prev_frame_probs.class_0_hp = current_frame_info.entropy.class_0_hp;
- prev_frame_probs.high_precision = current_frame_info.entropy.high_precision;
- }
- }
- writer.End();
- return writer.GetBuffer();
-}
-
-VpxBitStreamWriter VP9::ComposeUncompressedHeader() {
- VpxBitStreamWriter uncomp_writer{};
-
- uncomp_writer.WriteU(2, 2); // Frame marker.
- uncomp_writer.WriteU(0, 2); // Profile.
- uncomp_writer.WriteBit(false); // Show existing frame.
- uncomp_writer.WriteBit(!current_frame_info.is_key_frame); // is key frame?
- uncomp_writer.WriteBit(current_frame_info.show_frame); // show frame?
- uncomp_writer.WriteBit(current_frame_info.error_resilient_mode); // error reslience
-
- if (current_frame_info.is_key_frame) {
- uncomp_writer.WriteU(frame_sync_code, 24);
- uncomp_writer.WriteU(0, 3); // Color space.
- uncomp_writer.WriteU(0, 1); // Color range.
- uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16);
- uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16);
- uncomp_writer.WriteBit(false); // Render and frame size different.
-
- // Reset context
- prev_frame_probs = default_probs;
- swap_ref_indices = false;
- loop_filter_ref_deltas.fill(0);
- loop_filter_mode_deltas.fill(0);
- frame_ctxs.fill(default_probs);
-
- // intra only, meaning the frame can be recreated with no other references
- current_frame_info.intra_only = true;
- } else {
- if (!current_frame_info.show_frame) {
- uncomp_writer.WriteBit(current_frame_info.intra_only);
- } else {
- current_frame_info.intra_only = false;
- }
- if (!current_frame_info.error_resilient_mode) {
- uncomp_writer.WriteU(0, 2); // Reset frame context.
- }
- const auto& curr_offsets = current_frame_info.frame_offsets;
- const auto& next_offsets = next_frame.info.frame_offsets;
- const bool ref_frames_different = curr_offsets[1] != curr_offsets[2];
- const bool next_references_swap =
- (next_offsets[1] == curr_offsets[2]) || (next_offsets[2] == curr_offsets[1]);
- const bool needs_ref_swap = ref_frames_different && next_references_swap;
- if (needs_ref_swap) {
- swap_ref_indices = !swap_ref_indices;
- }
- union {
- u32 raw;
- BitField<0, 1, u32> refresh_last;
- BitField<1, 2, u32> refresh_golden;
- BitField<2, 1, u32> refresh_alt;
- } refresh_frame_flags;
-
- refresh_frame_flags.raw = 0;
- for (u32 index = 0; index < 3; ++index) {
- // Refresh indices that use the current frame as an index
- if (curr_offsets[3] == next_offsets[index]) {
- refresh_frame_flags.raw |= 1u << index;
- }
- }
- if (swap_ref_indices) {
- const u32 temp = refresh_frame_flags.refresh_golden;
- refresh_frame_flags.refresh_golden.Assign(refresh_frame_flags.refresh_alt.Value());
- refresh_frame_flags.refresh_alt.Assign(temp);
- }
- if (current_frame_info.intra_only) {
- uncomp_writer.WriteU(frame_sync_code, 24);
- uncomp_writer.WriteU(refresh_frame_flags.raw, 8);
- uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16);
- uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16);
- uncomp_writer.WriteBit(false); // Render and frame size different.
- } else {
- const bool swap_indices = needs_ref_swap ^ swap_ref_indices;
- const auto ref_frame_index = swap_indices ? std::array{0, 2, 1} : std::array{0, 1, 2};
- uncomp_writer.WriteU(refresh_frame_flags.raw, 8);
- for (size_t index = 1; index < 4; index++) {
- uncomp_writer.WriteU(ref_frame_index[index - 1], 3);
- uncomp_writer.WriteU(current_frame_info.ref_frame_sign_bias[index], 1);
- }
- uncomp_writer.WriteBit(true); // Frame size with refs.
- uncomp_writer.WriteBit(false); // Render and frame size different.
- uncomp_writer.WriteBit(current_frame_info.allow_high_precision_mv);
- uncomp_writer.WriteBit(current_frame_info.interp_filter == 4);
-
- if (current_frame_info.interp_filter != 4) {
- uncomp_writer.WriteU(current_frame_info.interp_filter, 2);
- }
- }
- }
-
- if (!current_frame_info.error_resilient_mode) {
- uncomp_writer.WriteBit(true); // Refresh frame context. where do i get this info from?
- uncomp_writer.WriteBit(true); // Frame parallel decoding mode.
- }
-
- int frame_ctx_idx = 0;
- if (!current_frame_info.show_frame) {
- frame_ctx_idx = 1;
- }
-
- uncomp_writer.WriteU(frame_ctx_idx, 2); // Frame context index.
- prev_frame_probs = frame_ctxs[frame_ctx_idx]; // reference probabilities for compressed header
- frame_ctxs[frame_ctx_idx] = current_frame_info.entropy;
-
- uncomp_writer.WriteU(current_frame_info.first_level, 6);
- uncomp_writer.WriteU(current_frame_info.sharpness_level, 3);
- uncomp_writer.WriteBit(current_frame_info.mode_ref_delta_enabled);
-
- if (current_frame_info.mode_ref_delta_enabled) {
- // check if ref deltas are different, update accordingly
- std::array<bool, 4> update_loop_filter_ref_deltas;
- std::array<bool, 2> update_loop_filter_mode_deltas;
-
- bool loop_filter_delta_update = false;
-
- for (std::size_t index = 0; index < current_frame_info.ref_deltas.size(); index++) {
- const s8 old_deltas = loop_filter_ref_deltas[index];
- const s8 new_deltas = current_frame_info.ref_deltas[index];
- const bool differing_delta = old_deltas != new_deltas;
-
- update_loop_filter_ref_deltas[index] = differing_delta;
- loop_filter_delta_update |= differing_delta;
- }
-
- for (std::size_t index = 0; index < current_frame_info.mode_deltas.size(); index++) {
- const s8 old_deltas = loop_filter_mode_deltas[index];
- const s8 new_deltas = current_frame_info.mode_deltas[index];
- const bool differing_delta = old_deltas != new_deltas;
-
- update_loop_filter_mode_deltas[index] = differing_delta;
- loop_filter_delta_update |= differing_delta;
- }
-
- uncomp_writer.WriteBit(loop_filter_delta_update);
-
- if (loop_filter_delta_update) {
- for (std::size_t index = 0; index < current_frame_info.ref_deltas.size(); index++) {
- uncomp_writer.WriteBit(update_loop_filter_ref_deltas[index]);
-
- if (update_loop_filter_ref_deltas[index]) {
- uncomp_writer.WriteS(current_frame_info.ref_deltas[index], 6);
- }
- }
-
- for (std::size_t index = 0; index < current_frame_info.mode_deltas.size(); index++) {
- uncomp_writer.WriteBit(update_loop_filter_mode_deltas[index]);
-
- if (update_loop_filter_mode_deltas[index]) {
- uncomp_writer.WriteS(current_frame_info.mode_deltas[index], 6);
- }
- }
- // save new deltas
- loop_filter_ref_deltas = current_frame_info.ref_deltas;
- loop_filter_mode_deltas = current_frame_info.mode_deltas;
- }
- }
-
- uncomp_writer.WriteU(current_frame_info.base_q_index, 8);
-
- uncomp_writer.WriteDeltaQ(current_frame_info.y_dc_delta_q);
- uncomp_writer.WriteDeltaQ(current_frame_info.uv_dc_delta_q);
- uncomp_writer.WriteDeltaQ(current_frame_info.uv_ac_delta_q);
-
- ASSERT(!current_frame_info.segment_enabled);
- uncomp_writer.WriteBit(false); // Segmentation enabled (TODO).
-
- const s32 min_tile_cols_log2 = CalcMinLog2TileCols(current_frame_info.frame_size.width);
- const s32 max_tile_cols_log2 = CalcMaxLog2TileCols(current_frame_info.frame_size.width);
-
- const s32 tile_cols_log2_diff = current_frame_info.log2_tile_cols - min_tile_cols_log2;
- const s32 tile_cols_log2_inc_mask = (1 << tile_cols_log2_diff) - 1;
-
- // If it's less than the maximum, we need to add an extra 0 on the bitstream
- // to indicate that it should stop reading.
- if (current_frame_info.log2_tile_cols < max_tile_cols_log2) {
- uncomp_writer.WriteU(tile_cols_log2_inc_mask << 1, tile_cols_log2_diff + 1);
- } else {
- uncomp_writer.WriteU(tile_cols_log2_inc_mask, tile_cols_log2_diff);
- }
-
- const bool tile_rows_log2_is_nonzero = current_frame_info.log2_tile_rows != 0;
-
- uncomp_writer.WriteBit(tile_rows_log2_is_nonzero);
-
- if (tile_rows_log2_is_nonzero) {
- uncomp_writer.WriteBit(current_frame_info.log2_tile_rows > 1);
- }
-
- return uncomp_writer;
-}
-
-void VP9::ComposeFrame(const NvdecCommon::NvdecRegisters& state) {
- std::vector<u8> bitstream;
- {
- Vp9FrameContainer curr_frame = GetCurrentFrame(state);
- current_frame_info = curr_frame.info;
- bitstream = std::move(curr_frame.bit_stream);
- }
- // The uncompressed header routine sets PrevProb parameters needed for the compressed header
- auto uncomp_writer = ComposeUncompressedHeader();
- std::vector<u8> compressed_header = ComposeCompressedHeader();
-
- uncomp_writer.WriteU(static_cast<s32>(compressed_header.size()), 16);
- uncomp_writer.Flush();
- std::vector<u8> uncompressed_header = uncomp_writer.GetByteArray();
-
- // Write headers and frame to buffer
- frame.resize(uncompressed_header.size() + compressed_header.size() + bitstream.size());
- std::copy(uncompressed_header.begin(), uncompressed_header.end(), frame.begin());
- std::copy(compressed_header.begin(), compressed_header.end(),
- frame.begin() + uncompressed_header.size());
- std::copy(bitstream.begin(), bitstream.end(),
- frame.begin() + uncompressed_header.size() + compressed_header.size());
-}
-
-VpxRangeEncoder::VpxRangeEncoder() {
- Write(false);
-}
-
-VpxRangeEncoder::~VpxRangeEncoder() = default;
-
-void VpxRangeEncoder::Write(s32 value, s32 value_size) {
- for (s32 bit = value_size - 1; bit >= 0; bit--) {
- Write(((value >> bit) & 1) != 0);
- }
-}
-
-void VpxRangeEncoder::Write(bool bit) {
- Write(bit, half_probability);
-}
-
-void VpxRangeEncoder::Write(bool bit, s32 probability) {
- u32 local_range = range;
- const u32 split = 1 + (((local_range - 1) * static_cast<u32>(probability)) >> 8);
- local_range = split;
-
- if (bit) {
- low_value += split;
- local_range = range - split;
- }
-
- s32 shift = norm_lut[local_range];
- local_range <<= shift;
- count += shift;
-
- if (count >= 0) {
- const s32 offset = shift - count;
-
- if (((low_value << (offset - 1)) >> 31) != 0) {
- const s32 current_pos = static_cast<s32>(base_stream.GetPosition());
- base_stream.Seek(-1, Common::SeekOrigin::FromCurrentPos);
- while (PeekByte() == 0xff) {
- base_stream.WriteByte(0);
-
- base_stream.Seek(-2, Common::SeekOrigin::FromCurrentPos);
- }
- base_stream.WriteByte(static_cast<u8>((PeekByte() + 1)));
- base_stream.Seek(current_pos, Common::SeekOrigin::SetOrigin);
- }
- base_stream.WriteByte(static_cast<u8>((low_value >> (24 - offset))));
-
- low_value <<= offset;
- shift = count;
- low_value &= 0xffffff;
- count -= 8;
- }
-
- low_value <<= shift;
- range = local_range;
-}
-
-void VpxRangeEncoder::End() {
- for (std::size_t index = 0; index < 32; ++index) {
- Write(false);
- }
-}
-
-u8 VpxRangeEncoder::PeekByte() {
- const u8 value = base_stream.ReadByte();
- base_stream.Seek(-1, Common::SeekOrigin::FromCurrentPos);
-
- return value;
-}
-
-VpxBitStreamWriter::VpxBitStreamWriter() = default;
-
-VpxBitStreamWriter::~VpxBitStreamWriter() = default;
-
-void VpxBitStreamWriter::WriteU(u32 value, u32 value_size) {
- WriteBits(value, value_size);
-}
-
-void VpxBitStreamWriter::WriteS(s32 value, u32 value_size) {
- const bool sign = value < 0;
- if (sign) {
- value = -value;
- }
-
- WriteBits(static_cast<u32>(value << 1) | (sign ? 1 : 0), value_size + 1);
-}
-
-void VpxBitStreamWriter::WriteDeltaQ(u32 value) {
- const bool delta_coded = value != 0;
- WriteBit(delta_coded);
-
- if (delta_coded) {
- WriteBits(value, 4);
- }
-}
-
-void VpxBitStreamWriter::WriteBits(u32 value, u32 bit_count) {
- s32 value_pos = 0;
- s32 remaining = bit_count;
-
- while (remaining > 0) {
- s32 copy_size = remaining;
-
- const s32 free = GetFreeBufferBits();
-
- if (copy_size > free) {
- copy_size = free;
- }
-
- const s32 mask = (1 << copy_size) - 1;
-
- const s32 src_shift = (bit_count - value_pos) - copy_size;
- const s32 dst_shift = (buffer_size - buffer_pos) - copy_size;
-
- buffer |= ((value >> src_shift) & mask) << dst_shift;
-
- value_pos += copy_size;
- buffer_pos += copy_size;
- remaining -= copy_size;
- }
-}
-
-void VpxBitStreamWriter::WriteBit(bool state) {
- WriteBits(state ? 1 : 0, 1);
-}
-
-s32 VpxBitStreamWriter::GetFreeBufferBits() {
- if (buffer_pos == buffer_size) {
- Flush();
- }
-
- return buffer_size - buffer_pos;
-}
-
-void VpxBitStreamWriter::Flush() {
- if (buffer_pos == 0) {
- return;
- }
- byte_array.push_back(static_cast<u8>(buffer));
- buffer = 0;
- buffer_pos = 0;
-}
-
-std::vector<u8>& VpxBitStreamWriter::GetByteArray() {
- return byte_array;
-}
-
-const std::vector<u8>& VpxBitStreamWriter::GetByteArray() const {
- return byte_array;
-}
-
-} // namespace Tegra::Decoder
diff --git a/src/video_core/command_classes/codecs/vp9.h b/src/video_core/command_classes/codecs/vp9.h
deleted file mode 100644
index 2e735c792..000000000
--- a/src/video_core/command_classes/codecs/vp9.h
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <vector>
-
-#include "common/common_types.h"
-#include "common/stream.h"
-#include "video_core/command_classes/codecs/vp9_types.h"
-#include "video_core/command_classes/nvdec_common.h"
-
-namespace Tegra {
-class GPU;
-namespace Decoder {
-
-/// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the
-/// VP9 header bitstreams.
-
-class VpxRangeEncoder {
-public:
- VpxRangeEncoder();
- ~VpxRangeEncoder();
-
- VpxRangeEncoder(const VpxRangeEncoder&) = delete;
- VpxRangeEncoder& operator=(const VpxRangeEncoder&) = delete;
-
- VpxRangeEncoder(VpxRangeEncoder&&) = default;
- VpxRangeEncoder& operator=(VpxRangeEncoder&&) = default;
-
- /// Writes the rightmost value_size bits from value into the stream
- void Write(s32 value, s32 value_size);
-
- /// Writes a single bit with half probability
- void Write(bool bit);
-
- /// Writes a bit to the base_stream encoded with probability
- void Write(bool bit, s32 probability);
-
- /// Signal the end of the bitstream
- void End();
-
- [[nodiscard]] std::vector<u8>& GetBuffer() {
- return base_stream.GetBuffer();
- }
-
- [[nodiscard]] const std::vector<u8>& GetBuffer() const {
- return base_stream.GetBuffer();
- }
-
-private:
- u8 PeekByte();
- Common::Stream base_stream{};
- u32 low_value{};
- u32 range{0xff};
- s32 count{-24};
- s32 half_probability{128};
-};
-
-class VpxBitStreamWriter {
-public:
- VpxBitStreamWriter();
- ~VpxBitStreamWriter();
-
- VpxBitStreamWriter(const VpxBitStreamWriter&) = delete;
- VpxBitStreamWriter& operator=(const VpxBitStreamWriter&) = delete;
-
- VpxBitStreamWriter(VpxBitStreamWriter&&) = default;
- VpxBitStreamWriter& operator=(VpxBitStreamWriter&&) = default;
-
- /// Write an unsigned integer value
- void WriteU(u32 value, u32 value_size);
-
- /// Write a signed integer value
- void WriteS(s32 value, u32 value_size);
-
- /// Based on 6.2.10 of VP9 Spec, writes a delta coded value
- void WriteDeltaQ(u32 value);
-
- /// Write a single bit.
- void WriteBit(bool state);
-
- /// Pushes current buffer into buffer_array, resets buffer
- void Flush();
-
- /// Returns byte_array
- [[nodiscard]] std::vector<u8>& GetByteArray();
-
- /// Returns const byte_array
- [[nodiscard]] const std::vector<u8>& GetByteArray() const;
-
-private:
- /// Write bit_count bits from value into buffer
- void WriteBits(u32 value, u32 bit_count);
-
- /// Gets next available position in buffer, invokes Flush() if buffer is full
- s32 GetFreeBufferBits();
-
- s32 buffer_size{8};
-
- s32 buffer{};
- s32 buffer_pos{};
- std::vector<u8> byte_array;
-};
-
-class VP9 {
-public:
- explicit VP9(GPU& gpu_);
- ~VP9();
-
- VP9(const VP9&) = delete;
- VP9& operator=(const VP9&) = delete;
-
- VP9(VP9&&) = default;
- VP9& operator=(VP9&&) = delete;
-
- /// Composes the VP9 frame from the GPU state information.
- /// Based on the official VP9 spec documentation
- void ComposeFrame(const NvdecCommon::NvdecRegisters& state);
-
- /// Returns true if the most recent frame was a hidden frame.
- [[nodiscard]] bool WasFrameHidden() const {
- return !current_frame_info.show_frame;
- }
-
- /// Returns a const reference to the composed frame data.
- [[nodiscard]] const std::vector<u8>& GetFrameBytes() const {
- return frame;
- }
-
-private:
- /// Generates compressed header probability updates in the bitstream writer
- template <typename T, std::size_t N>
- void WriteProbabilityUpdate(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
- const std::array<T, N>& old_prob);
-
- /// Generates compressed header probability updates in the bitstream writer
- /// If probs are not equal, WriteProbabilityDelta is invoked
- void WriteProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
-
- /// Generates compressed header probability deltas in the bitstream writer
- void WriteProbabilityDelta(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
-
- /// Inverse of 6.3.4 Decode term subexp
- void EncodeTermSubExp(VpxRangeEncoder& writer, s32 value);
-
- /// Writes if the value is less than the test value
- bool WriteLessThan(VpxRangeEncoder& writer, s32 value, s32 test);
-
- /// Writes probability updates for the Coef probabilities
- void WriteCoefProbabilityUpdate(VpxRangeEncoder& writer, s32 tx_mode,
- const std::array<u8, 1728>& new_prob,
- const std::array<u8, 1728>& old_prob);
-
- /// Write probabilities for 4-byte aligned structures
- template <typename T, std::size_t N>
- void WriteProbabilityUpdateAligned4(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
- const std::array<T, N>& old_prob);
-
- /// Write motion vector probability updates. 6.3.17 in the spec
- void WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
-
- /// Returns VP9 information from NVDEC provided offset and size
- [[nodiscard]] Vp9PictureInfo GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state);
-
- /// Read and convert NVDEC provided entropy probs to Vp9EntropyProbs struct
- void InsertEntropy(u64 offset, Vp9EntropyProbs& dst);
-
- /// Returns frame to be decoded after buffering
- [[nodiscard]] Vp9FrameContainer GetCurrentFrame(const NvdecCommon::NvdecRegisters& state);
-
- /// Use NVDEC providied information to compose the headers for the current frame
- [[nodiscard]] std::vector<u8> ComposeCompressedHeader();
- [[nodiscard]] VpxBitStreamWriter ComposeUncompressedHeader();
-
- GPU& gpu;
- std::vector<u8> frame;
-
- std::array<s8, 4> loop_filter_ref_deltas{};
- std::array<s8, 2> loop_filter_mode_deltas{};
-
- Vp9FrameContainer next_frame{};
- std::array<Vp9EntropyProbs, 4> frame_ctxs{};
- bool swap_ref_indices{};
-
- Vp9PictureInfo current_frame_info{};
- Vp9EntropyProbs prev_frame_probs{};
-};
-
-} // namespace Decoder
-} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/vp9_types.h b/src/video_core/command_classes/codecs/vp9_types.h
deleted file mode 100644
index 3b1ed4b3a..000000000
--- a/src/video_core/command_classes/codecs/vp9_types.h
+++ /dev/null
@@ -1,308 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <cstring>
-#include <vector>
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-
-namespace Tegra {
-class GPU;
-
-namespace Decoder {
-struct Vp9FrameDimensions {
- s16 width;
- s16 height;
- s16 luma_pitch;
- s16 chroma_pitch;
-};
-static_assert(sizeof(Vp9FrameDimensions) == 0x8, "Vp9 Vp9FrameDimensions is an invalid size");
-
-enum class FrameFlags : u32 {
- IsKeyFrame = 1 << 0,
- LastFrameIsKeyFrame = 1 << 1,
- FrameSizeChanged = 1 << 2,
- ErrorResilientMode = 1 << 3,
- LastShowFrame = 1 << 4,
- IntraOnly = 1 << 5,
-};
-DECLARE_ENUM_FLAG_OPERATORS(FrameFlags)
-
-enum class TxSize {
- Tx4x4 = 0, // 4x4 transform
- Tx8x8 = 1, // 8x8 transform
- Tx16x16 = 2, // 16x16 transform
- Tx32x32 = 3, // 32x32 transform
- TxSizes = 4
-};
-
-enum class TxMode {
- Only4X4 = 0, // Only 4x4 transform used
- Allow8X8 = 1, // Allow block transform size up to 8x8
- Allow16X16 = 2, // Allow block transform size up to 16x16
- Allow32X32 = 3, // Allow block transform size up to 32x32
- TxModeSelect = 4, // Transform specified for each block
- TxModes = 5
-};
-
-struct Segmentation {
- u8 enabled;
- u8 update_map;
- u8 temporal_update;
- u8 abs_delta;
- std::array<u32, 8> feature_mask;
- std::array<std::array<s16, 4>, 8> feature_data;
-};
-static_assert(sizeof(Segmentation) == 0x64, "Segmentation is an invalid size");
-
-struct LoopFilter {
- u8 mode_ref_delta_enabled;
- std::array<s8, 4> ref_deltas;
- std::array<s8, 2> mode_deltas;
-};
-static_assert(sizeof(LoopFilter) == 0x7, "LoopFilter is an invalid size");
-
-struct Vp9EntropyProbs {
- std::array<u8, 36> y_mode_prob; ///< 0x0000
- std::array<u8, 64> partition_prob; ///< 0x0024
- std::array<u8, 1728> coef_probs; ///< 0x0064
- std::array<u8, 8> switchable_interp_prob; ///< 0x0724
- std::array<u8, 28> inter_mode_prob; ///< 0x072C
- std::array<u8, 4> intra_inter_prob; ///< 0x0748
- std::array<u8, 5> comp_inter_prob; ///< 0x074C
- std::array<u8, 10> single_ref_prob; ///< 0x0751
- std::array<u8, 5> comp_ref_prob; ///< 0x075B
- std::array<u8, 6> tx_32x32_prob; ///< 0x0760
- std::array<u8, 4> tx_16x16_prob; ///< 0x0766
- std::array<u8, 2> tx_8x8_prob; ///< 0x076A
- std::array<u8, 3> skip_probs; ///< 0x076C
- std::array<u8, 3> joints; ///< 0x076F
- std::array<u8, 2> sign; ///< 0x0772
- std::array<u8, 20> classes; ///< 0x0774
- std::array<u8, 2> class_0; ///< 0x0788
- std::array<u8, 20> prob_bits; ///< 0x078A
- std::array<u8, 12> class_0_fr; ///< 0x079E
- std::array<u8, 6> fr; ///< 0x07AA
- std::array<u8, 2> class_0_hp; ///< 0x07B0
- std::array<u8, 2> high_precision; ///< 0x07B2
-};
-static_assert(sizeof(Vp9EntropyProbs) == 0x7B4, "Vp9EntropyProbs is an invalid size");
-
-struct Vp9PictureInfo {
- u32 bitstream_size;
- std::array<u64, 4> frame_offsets;
- std::array<s8, 4> ref_frame_sign_bias;
- s32 base_q_index;
- s32 y_dc_delta_q;
- s32 uv_dc_delta_q;
- s32 uv_ac_delta_q;
- s32 transform_mode;
- s32 interp_filter;
- s32 reference_mode;
- s32 log2_tile_cols;
- s32 log2_tile_rows;
- std::array<s8, 4> ref_deltas;
- std::array<s8, 2> mode_deltas;
- Vp9EntropyProbs entropy;
- Vp9FrameDimensions frame_size;
- u8 first_level;
- u8 sharpness_level;
- bool is_key_frame;
- bool intra_only;
- bool last_frame_was_key;
- bool error_resilient_mode;
- bool last_frame_shown;
- bool show_frame;
- bool lossless;
- bool allow_high_precision_mv;
- bool segment_enabled;
- bool mode_ref_delta_enabled;
-};
-
-struct Vp9FrameContainer {
- Vp9PictureInfo info{};
- std::vector<u8> bit_stream;
-};
-
-struct PictureInfo {
- INSERT_PADDING_WORDS_NOINIT(12); ///< 0x00
- u32 bitstream_size; ///< 0x30
- INSERT_PADDING_WORDS_NOINIT(5); ///< 0x34
- Vp9FrameDimensions last_frame_size; ///< 0x48
- Vp9FrameDimensions golden_frame_size; ///< 0x50
- Vp9FrameDimensions alt_frame_size; ///< 0x58
- Vp9FrameDimensions current_frame_size; ///< 0x60
- FrameFlags vp9_flags; ///< 0x68
- std::array<s8, 4> ref_frame_sign_bias; ///< 0x6C
- u8 first_level; ///< 0x70
- u8 sharpness_level; ///< 0x71
- u8 base_q_index; ///< 0x72
- u8 y_dc_delta_q; ///< 0x73
- u8 uv_ac_delta_q; ///< 0x74
- u8 uv_dc_delta_q; ///< 0x75
- u8 lossless; ///< 0x76
- u8 tx_mode; ///< 0x77
- u8 allow_high_precision_mv; ///< 0x78
- u8 interp_filter; ///< 0x79
- u8 reference_mode; ///< 0x7A
- INSERT_PADDING_BYTES_NOINIT(3); ///< 0x7B
- u8 log2_tile_cols; ///< 0x7E
- u8 log2_tile_rows; ///< 0x7F
- Segmentation segmentation; ///< 0x80
- LoopFilter loop_filter; ///< 0xE4
- INSERT_PADDING_BYTES_NOINIT(21); ///< 0xEB
-
- [[nodiscard]] Vp9PictureInfo Convert() const {
- return {
- .bitstream_size = bitstream_size,
- .frame_offsets{},
- .ref_frame_sign_bias = ref_frame_sign_bias,
- .base_q_index = base_q_index,
- .y_dc_delta_q = y_dc_delta_q,
- .uv_dc_delta_q = uv_dc_delta_q,
- .uv_ac_delta_q = uv_ac_delta_q,
- .transform_mode = tx_mode,
- .interp_filter = interp_filter,
- .reference_mode = reference_mode,
- .log2_tile_cols = log2_tile_cols,
- .log2_tile_rows = log2_tile_rows,
- .ref_deltas = loop_filter.ref_deltas,
- .mode_deltas = loop_filter.mode_deltas,
- .entropy{},
- .frame_size = current_frame_size,
- .first_level = first_level,
- .sharpness_level = sharpness_level,
- .is_key_frame = True(vp9_flags & FrameFlags::IsKeyFrame),
- .intra_only = True(vp9_flags & FrameFlags::IntraOnly),
- .last_frame_was_key = True(vp9_flags & FrameFlags::LastFrameIsKeyFrame),
- .error_resilient_mode = True(vp9_flags & FrameFlags::ErrorResilientMode),
- .last_frame_shown = True(vp9_flags & FrameFlags::LastShowFrame),
- .show_frame = true,
- .lossless = lossless != 0,
- .allow_high_precision_mv = allow_high_precision_mv != 0,
- .segment_enabled = segmentation.enabled != 0,
- .mode_ref_delta_enabled = loop_filter.mode_ref_delta_enabled != 0,
- };
- }
-};
-static_assert(sizeof(PictureInfo) == 0x100, "PictureInfo is an invalid size");
-
-struct EntropyProbs {
- INSERT_PADDING_BYTES_NOINIT(1024); ///< 0x0000
- std::array<u8, 28> inter_mode_prob; ///< 0x0400
- std::array<u8, 4> intra_inter_prob; ///< 0x041C
- INSERT_PADDING_BYTES_NOINIT(80); ///< 0x0420
- std::array<u8, 2> tx_8x8_prob; ///< 0x0470
- std::array<u8, 4> tx_16x16_prob; ///< 0x0472
- std::array<u8, 6> tx_32x32_prob; ///< 0x0476
- std::array<u8, 4> y_mode_prob_e8; ///< 0x047C
- std::array<std::array<u8, 8>, 4> y_mode_prob_e0e7; ///< 0x0480
- INSERT_PADDING_BYTES_NOINIT(64); ///< 0x04A0
- std::array<u8, 64> partition_prob; ///< 0x04E0
- INSERT_PADDING_BYTES_NOINIT(10); ///< 0x0520
- std::array<u8, 8> switchable_interp_prob; ///< 0x052A
- std::array<u8, 5> comp_inter_prob; ///< 0x0532
- std::array<u8, 3> skip_probs; ///< 0x0537
- INSERT_PADDING_BYTES_NOINIT(1); ///< 0x053A
- std::array<u8, 3> joints; ///< 0x053B
- std::array<u8, 2> sign; ///< 0x053E
- std::array<u8, 2> class_0; ///< 0x0540
- std::array<u8, 6> fr; ///< 0x0542
- std::array<u8, 2> class_0_hp; ///< 0x0548
- std::array<u8, 2> high_precision; ///< 0x054A
- std::array<u8, 20> classes; ///< 0x054C
- std::array<u8, 12> class_0_fr; ///< 0x0560
- std::array<u8, 20> pred_bits; ///< 0x056C
- std::array<u8, 10> single_ref_prob; ///< 0x0580
- std::array<u8, 5> comp_ref_prob; ///< 0x058A
- INSERT_PADDING_BYTES_NOINIT(17); ///< 0x058F
- std::array<u8, 2304> coef_probs; ///< 0x05A0
-
- void Convert(Vp9EntropyProbs& fc) {
- fc.inter_mode_prob = inter_mode_prob;
- fc.intra_inter_prob = intra_inter_prob;
- fc.tx_8x8_prob = tx_8x8_prob;
- fc.tx_16x16_prob = tx_16x16_prob;
- fc.tx_32x32_prob = tx_32x32_prob;
-
- for (std::size_t i = 0; i < 4; i++) {
- for (std::size_t j = 0; j < 9; j++) {
- fc.y_mode_prob[j + 9 * i] = j < 8 ? y_mode_prob_e0e7[i][j] : y_mode_prob_e8[i];
- }
- }
-
- fc.partition_prob = partition_prob;
- fc.switchable_interp_prob = switchable_interp_prob;
- fc.comp_inter_prob = comp_inter_prob;
- fc.skip_probs = skip_probs;
- fc.joints = joints;
- fc.sign = sign;
- fc.class_0 = class_0;
- fc.fr = fr;
- fc.class_0_hp = class_0_hp;
- fc.high_precision = high_precision;
- fc.classes = classes;
- fc.class_0_fr = class_0_fr;
- fc.prob_bits = pred_bits;
- fc.single_ref_prob = single_ref_prob;
- fc.comp_ref_prob = comp_ref_prob;
-
- // Skip the 4th element as it goes unused
- for (std::size_t i = 0; i < coef_probs.size(); i += 4) {
- const std::size_t j = i - i / 4;
- fc.coef_probs[j] = coef_probs[i];
- fc.coef_probs[j + 1] = coef_probs[i + 1];
- fc.coef_probs[j + 2] = coef_probs[i + 2];
- }
- }
-};
-static_assert(sizeof(EntropyProbs) == 0xEA0, "EntropyProbs is an invalid size");
-
-enum class Ref { Last, Golden, AltRef };
-
-struct RefPoolElement {
- s64 frame{};
- Ref ref{};
- bool refresh{};
-};
-
-#define ASSERT_POSITION(field_name, position) \
- static_assert(offsetof(Vp9EntropyProbs, field_name) == position, \
- "Field " #field_name " has invalid position")
-
-ASSERT_POSITION(partition_prob, 0x0024);
-ASSERT_POSITION(switchable_interp_prob, 0x0724);
-ASSERT_POSITION(sign, 0x0772);
-ASSERT_POSITION(class_0_fr, 0x079E);
-ASSERT_POSITION(high_precision, 0x07B2);
-#undef ASSERT_POSITION
-
-#define ASSERT_POSITION(field_name, position) \
- static_assert(offsetof(PictureInfo, field_name) == position, \
- "Field " #field_name " has invalid position")
-
-ASSERT_POSITION(bitstream_size, 0x30);
-ASSERT_POSITION(last_frame_size, 0x48);
-ASSERT_POSITION(first_level, 0x70);
-ASSERT_POSITION(segmentation, 0x80);
-ASSERT_POSITION(loop_filter, 0xE4);
-#undef ASSERT_POSITION
-
-#define ASSERT_POSITION(field_name, position) \
- static_assert(offsetof(EntropyProbs, field_name) == position, \
- "Field " #field_name " has invalid position")
-
-ASSERT_POSITION(inter_mode_prob, 0x400);
-ASSERT_POSITION(tx_8x8_prob, 0x470);
-ASSERT_POSITION(partition_prob, 0x4E0);
-ASSERT_POSITION(class_0, 0x540);
-ASSERT_POSITION(class_0_fr, 0x560);
-ASSERT_POSITION(coef_probs, 0x5A0);
-#undef ASSERT_POSITION
-
-}; // namespace Decoder
-}; // namespace Tegra
diff --git a/src/video_core/command_classes/host1x.cpp b/src/video_core/command_classes/host1x.cpp
deleted file mode 100644
index b12494528..000000000
--- a/src/video_core/command_classes/host1x.cpp
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/assert.h"
-#include "video_core/command_classes/host1x.h"
-#include "video_core/gpu.h"
-
-Tegra::Host1x::Host1x(GPU& gpu_) : gpu(gpu_) {}
-
-Tegra::Host1x::~Host1x() = default;
-
-void Tegra::Host1x::ProcessMethod(Method method, u32 argument) {
- switch (method) {
- case Method::LoadSyncptPayload32:
- syncpoint_value = argument;
- break;
- case Method::WaitSyncpt:
- case Method::WaitSyncpt32:
- Execute(argument);
- break;
- default:
- UNIMPLEMENTED_MSG("Host1x method 0x{:X}", static_cast<u32>(method));
- break;
- }
-}
-
-void Tegra::Host1x::Execute(u32 data) {
- gpu.WaitFence(data, syncpoint_value);
-}
diff --git a/src/video_core/command_classes/host1x.h b/src/video_core/command_classes/host1x.h
deleted file mode 100644
index 7e94799dd..000000000
--- a/src/video_core/command_classes/host1x.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <vector>
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-
-namespace Tegra {
-class GPU;
-class Nvdec;
-
-class Host1x {
-public:
- enum class Method : u32 {
- WaitSyncpt = 0x8,
- LoadSyncptPayload32 = 0x4e,
- WaitSyncpt32 = 0x50,
- };
-
- explicit Host1x(GPU& gpu);
- ~Host1x();
-
- /// Writes the method into the state, Invoke Execute() if encountered
- void ProcessMethod(Method method, u32 argument);
-
-private:
- /// For Host1x, execute is waiting on a syncpoint previously written into the state
- void Execute(u32 data);
-
- u32 syncpoint_value{};
- GPU& gpu;
-};
-
-} // namespace Tegra
diff --git a/src/video_core/command_classes/nvdec.cpp b/src/video_core/command_classes/nvdec.cpp
deleted file mode 100644
index 9aaf5247e..000000000
--- a/src/video_core/command_classes/nvdec.cpp
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/assert.h"
-#include "video_core/command_classes/nvdec.h"
-#include "video_core/gpu.h"
-
-namespace Tegra {
-
-#define NVDEC_REG_INDEX(field_name) \
- (offsetof(NvdecCommon::NvdecRegisters, field_name) / sizeof(u64))
-
-Nvdec::Nvdec(GPU& gpu_) : gpu(gpu_), state{}, codec(std::make_unique<Codec>(gpu, state)) {}
-
-Nvdec::~Nvdec() = default;
-
-void Nvdec::ProcessMethod(u32 method, u32 argument) {
- state.reg_array[method] = static_cast<u64>(argument) << 8;
-
- switch (method) {
- case NVDEC_REG_INDEX(set_codec_id):
- codec->SetTargetCodec(static_cast<NvdecCommon::VideoCodec>(argument));
- break;
- case NVDEC_REG_INDEX(execute):
- Execute();
- break;
- }
-}
-
-AVFramePtr Nvdec::GetFrame() {
- return codec->GetCurrentFrame();
-}
-
-void Nvdec::Execute() {
- switch (codec->GetCurrentCodec()) {
- case NvdecCommon::VideoCodec::H264:
- case NvdecCommon::VideoCodec::VP8:
- case NvdecCommon::VideoCodec::VP9:
- codec->Decode();
- break;
- default:
- UNIMPLEMENTED_MSG("Codec {}", codec->GetCurrentCodecName());
- break;
- }
-}
-
-} // namespace Tegra
diff --git a/src/video_core/command_classes/nvdec.h b/src/video_core/command_classes/nvdec.h
deleted file mode 100644
index 6e1da0b04..000000000
--- a/src/video_core/command_classes/nvdec.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <vector>
-#include "common/common_types.h"
-#include "video_core/command_classes/codecs/codec.h"
-
-namespace Tegra {
-class GPU;
-
-class Nvdec {
-public:
- explicit Nvdec(GPU& gpu);
- ~Nvdec();
-
- /// Writes the method into the state, Invoke Execute() if encountered
- void ProcessMethod(u32 method, u32 argument);
-
- /// Return most recently decoded frame
- [[nodiscard]] AVFramePtr GetFrame();
-
-private:
- /// Invoke codec to decode a frame
- void Execute();
-
- GPU& gpu;
- NvdecCommon::NvdecRegisters state;
- std::unique_ptr<Codec> codec;
-};
-} // namespace Tegra
diff --git a/src/video_core/command_classes/nvdec_common.h b/src/video_core/command_classes/nvdec_common.h
deleted file mode 100644
index 8a35c44a1..000000000
--- a/src/video_core/command_classes/nvdec_common.h
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/bit_field.h"
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-
-namespace Tegra::NvdecCommon {
-
-enum class VideoCodec : u64 {
- None = 0x0,
- H264 = 0x3,
- VP8 = 0x5,
- H265 = 0x7,
- VP9 = 0x9,
-};
-
-// NVDEC should use a 32-bit address space, but is mapped to 64-bit,
-// doubling the sizes here is compensating for that.
-struct NvdecRegisters {
- static constexpr std::size_t NUM_REGS = 0x178;
-
- union {
- struct {
- INSERT_PADDING_WORDS_NOINIT(256); ///< 0x0000
- VideoCodec set_codec_id; ///< 0x0400
- INSERT_PADDING_WORDS_NOINIT(126); ///< 0x0408
- u64 execute; ///< 0x0600
- INSERT_PADDING_WORDS_NOINIT(126); ///< 0x0608
- struct { ///< 0x0800
- union {
- BitField<0, 3, VideoCodec> codec;
- BitField<4, 1, u64> gp_timer_on;
- BitField<13, 1, u64> mb_timer_on;
- BitField<14, 1, u64> intra_frame_pslc;
- BitField<17, 1, u64> all_intra_frame;
- };
- } control_params;
- u64 picture_info_offset; ///< 0x0808
- u64 frame_bitstream_offset; ///< 0x0810
- u64 frame_number; ///< 0x0818
- u64 h264_slice_data_offsets; ///< 0x0820
- u64 h264_mv_dump_offset; ///< 0x0828
- INSERT_PADDING_WORDS_NOINIT(6); ///< 0x0830
- u64 frame_stats_offset; ///< 0x0848
- u64 h264_last_surface_luma_offset; ///< 0x0850
- u64 h264_last_surface_chroma_offset; ///< 0x0858
- std::array<u64, 17> surface_luma_offset; ///< 0x0860
- std::array<u64, 17> surface_chroma_offset; ///< 0x08E8
- INSERT_PADDING_WORDS_NOINIT(68); ///< 0x0970
- u64 vp8_prob_data_offset; ///< 0x0A80
- u64 vp8_header_partition_buf_offset; ///< 0x0A88
- INSERT_PADDING_WORDS_NOINIT(60); ///< 0x0A90
- u64 vp9_entropy_probs_offset; ///< 0x0B80
- u64 vp9_backward_updates_offset; ///< 0x0B88
- u64 vp9_last_frame_segmap_offset; ///< 0x0B90
- u64 vp9_curr_frame_segmap_offset; ///< 0x0B98
- INSERT_PADDING_WORDS_NOINIT(2); ///< 0x0BA0
- u64 vp9_last_frame_mvs_offset; ///< 0x0BA8
- u64 vp9_curr_frame_mvs_offset; ///< 0x0BB0
- INSERT_PADDING_WORDS_NOINIT(2); ///< 0x0BB8
- };
- std::array<u64, NUM_REGS> reg_array;
- };
-};
-static_assert(sizeof(NvdecRegisters) == (0xBC0), "NvdecRegisters is incorrect size");
-
-#define ASSERT_REG_POSITION(field_name, position) \
- static_assert(offsetof(NvdecRegisters, field_name) == position * sizeof(u64), \
- "Field " #field_name " has invalid position")
-
-ASSERT_REG_POSITION(set_codec_id, 0x80);
-ASSERT_REG_POSITION(execute, 0xC0);
-ASSERT_REG_POSITION(control_params, 0x100);
-ASSERT_REG_POSITION(picture_info_offset, 0x101);
-ASSERT_REG_POSITION(frame_bitstream_offset, 0x102);
-ASSERT_REG_POSITION(frame_number, 0x103);
-ASSERT_REG_POSITION(h264_slice_data_offsets, 0x104);
-ASSERT_REG_POSITION(frame_stats_offset, 0x109);
-ASSERT_REG_POSITION(h264_last_surface_luma_offset, 0x10A);
-ASSERT_REG_POSITION(h264_last_surface_chroma_offset, 0x10B);
-ASSERT_REG_POSITION(surface_luma_offset, 0x10C);
-ASSERT_REG_POSITION(surface_chroma_offset, 0x11D);
-ASSERT_REG_POSITION(vp8_prob_data_offset, 0x150);
-ASSERT_REG_POSITION(vp8_header_partition_buf_offset, 0x151);
-ASSERT_REG_POSITION(vp9_entropy_probs_offset, 0x170);
-ASSERT_REG_POSITION(vp9_backward_updates_offset, 0x171);
-ASSERT_REG_POSITION(vp9_last_frame_segmap_offset, 0x172);
-ASSERT_REG_POSITION(vp9_curr_frame_segmap_offset, 0x173);
-ASSERT_REG_POSITION(vp9_last_frame_mvs_offset, 0x175);
-ASSERT_REG_POSITION(vp9_curr_frame_mvs_offset, 0x176);
-
-#undef ASSERT_REG_POSITION
-
-} // namespace Tegra::NvdecCommon
diff --git a/src/video_core/command_classes/sync_manager.cpp b/src/video_core/command_classes/sync_manager.cpp
deleted file mode 100644
index 19dc9e0ab..000000000
--- a/src/video_core/command_classes/sync_manager.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-// MIT License
-//
-// Copyright (c) Ryujinx Team and Contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
-// associated documentation files (the "Software"), to deal in the Software without restriction,
-// including without limitation the rights to use, copy, modify, merge, publish, distribute,
-// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all copies or
-// substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
-// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-#include <algorithm>
-#include "sync_manager.h"
-#include "video_core/gpu.h"
-
-namespace Tegra {
-SyncptIncrManager::SyncptIncrManager(GPU& gpu_) : gpu(gpu_) {}
-SyncptIncrManager::~SyncptIncrManager() = default;
-
-void SyncptIncrManager::Increment(u32 id) {
- increments.emplace_back(0, 0, id, true);
- IncrementAllDone();
-}
-
-u32 SyncptIncrManager::IncrementWhenDone(u32 class_id, u32 id) {
- const u32 handle = current_id++;
- increments.emplace_back(handle, class_id, id);
- return handle;
-}
-
-void SyncptIncrManager::SignalDone(u32 handle) {
- const auto done_incr =
- std::find_if(increments.begin(), increments.end(),
- [handle](const SyncptIncr& incr) { return incr.id == handle; });
- if (done_incr != increments.cend()) {
- done_incr->complete = true;
- }
- IncrementAllDone();
-}
-
-void SyncptIncrManager::IncrementAllDone() {
- std::size_t done_count = 0;
- for (; done_count < increments.size(); ++done_count) {
- if (!increments[done_count].complete) {
- break;
- }
- gpu.IncrementSyncPoint(increments[done_count].syncpt_id);
- }
- increments.erase(increments.begin(), increments.begin() + done_count);
-}
-} // namespace Tegra
diff --git a/src/video_core/command_classes/sync_manager.h b/src/video_core/command_classes/sync_manager.h
deleted file mode 100644
index 2c321ec58..000000000
--- a/src/video_core/command_classes/sync_manager.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// MIT License
-//
-// Copyright (c) Ryujinx Team and Contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
-// associated documentation files (the "Software"), to deal in the Software without restriction,
-// including without limitation the rights to use, copy, modify, merge, publish, distribute,
-// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all copies or
-// substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
-// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-#pragma once
-
-#include <mutex>
-#include <vector>
-#include "common/common_types.h"
-
-namespace Tegra {
-class GPU;
-struct SyncptIncr {
- u32 id;
- u32 class_id;
- u32 syncpt_id;
- bool complete;
-
- SyncptIncr(u32 id_, u32 class_id_, u32 syncpt_id_, bool done = false)
- : id(id_), class_id(class_id_), syncpt_id(syncpt_id_), complete(done) {}
-};
-
-class SyncptIncrManager {
-public:
- explicit SyncptIncrManager(GPU& gpu);
- ~SyncptIncrManager();
-
- /// Add syncpoint id and increment all
- void Increment(u32 id);
-
- /// Returns a handle to increment later
- u32 IncrementWhenDone(u32 class_id, u32 id);
-
- /// IncrememntAllDone, including handle
- void SignalDone(u32 handle);
-
- /// Increment all sequential pending increments that are already done.
- void IncrementAllDone();
-
-private:
- std::vector<SyncptIncr> increments;
- std::mutex increment_lock;
- u32 current_id{};
-
- GPU& gpu;
-};
-
-} // namespace Tegra
diff --git a/src/video_core/command_classes/vic.cpp b/src/video_core/command_classes/vic.cpp
deleted file mode 100644
index 051616124..000000000
--- a/src/video_core/command_classes/vic.cpp
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <array>
-
-extern "C" {
-#if defined(__GNUC__) || defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wconversion"
-#endif
-#include <libswscale/swscale.h>
-#if defined(__GNUC__) || defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
-}
-
-#include "common/assert.h"
-#include "common/bit_field.h"
-#include "common/logging/log.h"
-
-#include "video_core/command_classes/nvdec.h"
-#include "video_core/command_classes/vic.h"
-#include "video_core/engines/maxwell_3d.h"
-#include "video_core/gpu.h"
-#include "video_core/memory_manager.h"
-#include "video_core/textures/decoders.h"
-
-namespace Tegra {
-namespace {
-enum class VideoPixelFormat : u64_le {
- RGBA8 = 0x1f,
- BGRA8 = 0x20,
- RGBX8 = 0x23,
- YUV420 = 0x44,
-};
-} // Anonymous namespace
-
-union VicConfig {
- u64_le raw{};
- BitField<0, 7, VideoPixelFormat> pixel_format;
- BitField<7, 2, u64_le> chroma_loc_horiz;
- BitField<9, 2, u64_le> chroma_loc_vert;
- BitField<11, 4, u64_le> block_linear_kind;
- BitField<15, 4, u64_le> block_linear_height_log2;
- BitField<32, 14, u64_le> surface_width_minus1;
- BitField<46, 14, u64_le> surface_height_minus1;
-};
-
-Vic::Vic(GPU& gpu_, std::shared_ptr<Nvdec> nvdec_processor_)
- : gpu(gpu_),
- nvdec_processor(std::move(nvdec_processor_)), converted_frame_buffer{nullptr, av_free} {}
-
-Vic::~Vic() = default;
-
-void Vic::ProcessMethod(Method method, u32 argument) {
- LOG_DEBUG(HW_GPU, "Vic method 0x{:X}", static_cast<u32>(method));
- const u64 arg = static_cast<u64>(argument) << 8;
- switch (method) {
- case Method::Execute:
- Execute();
- break;
- case Method::SetConfigStructOffset:
- config_struct_address = arg;
- break;
- case Method::SetOutputSurfaceLumaOffset:
- output_surface_luma_address = arg;
- break;
- case Method::SetOutputSurfaceChromaOffset:
- output_surface_chroma_address = arg;
- break;
- default:
- break;
- }
-}
-
-void Vic::Execute() {
- if (output_surface_luma_address == 0) {
- LOG_ERROR(Service_NVDRV, "VIC Luma address not set.");
- return;
- }
- const VicConfig config{gpu.MemoryManager().Read<u64>(config_struct_address + 0x20)};
- const AVFramePtr frame_ptr = nvdec_processor->GetFrame();
- const auto* frame = frame_ptr.get();
- if (!frame) {
- return;
- }
- const u64 surface_width = config.surface_width_minus1 + 1;
- const u64 surface_height = config.surface_height_minus1 + 1;
- if (static_cast<u64>(frame->width) != surface_width ||
- static_cast<u64>(frame->height) != surface_height) {
- // TODO: Properly support multiple video streams with differing frame dimensions
- LOG_WARNING(Service_NVDRV, "Frame dimensions {}x{} don't match surface dimensions {}x{}",
- frame->width, frame->height, surface_width, surface_height);
- }
- switch (config.pixel_format) {
- case VideoPixelFormat::RGBA8:
- case VideoPixelFormat::BGRA8:
- case VideoPixelFormat::RGBX8:
- WriteRGBFrame(frame, config);
- break;
- case VideoPixelFormat::YUV420:
- WriteYUVFrame(frame, config);
- break;
- default:
- UNIMPLEMENTED_MSG("Unknown video pixel format {:X}", config.pixel_format.Value());
- break;
- }
-}
-
-void Vic::WriteRGBFrame(const AVFrame* frame, const VicConfig& config) {
- LOG_TRACE(Service_NVDRV, "Writing RGB Frame");
-
- if (!scaler_ctx || frame->width != scaler_width || frame->height != scaler_height) {
- const AVPixelFormat target_format = [pixel_format = config.pixel_format]() {
- switch (pixel_format) {
- case VideoPixelFormat::RGBA8:
- return AV_PIX_FMT_RGBA;
- case VideoPixelFormat::BGRA8:
- return AV_PIX_FMT_BGRA;
- case VideoPixelFormat::RGBX8:
- return AV_PIX_FMT_RGB0;
- default:
- return AV_PIX_FMT_RGBA;
- }
- }();
-
- sws_freeContext(scaler_ctx);
- // Frames are decoded into either YUV420 or NV12 formats. Convert to desired RGB format
- scaler_ctx = sws_getContext(frame->width, frame->height,
- static_cast<AVPixelFormat>(frame->format), frame->width,
- frame->height, target_format, 0, nullptr, nullptr, nullptr);
- scaler_width = frame->width;
- scaler_height = frame->height;
- converted_frame_buffer.reset();
- }
- if (!converted_frame_buffer) {
- const size_t frame_size = frame->width * frame->height * 4;
- converted_frame_buffer = AVMallocPtr{static_cast<u8*>(av_malloc(frame_size)), av_free};
- }
- const std::array<int, 4> converted_stride{frame->width * 4, frame->height * 4, 0, 0};
- u8* const converted_frame_buf_addr{converted_frame_buffer.get()};
- sws_scale(scaler_ctx, frame->data, frame->linesize, 0, frame->height, &converted_frame_buf_addr,
- converted_stride.data());
-
- // Use the minimum of surface/frame dimensions to avoid buffer overflow.
- const u32 surface_width = static_cast<u32>(config.surface_width_minus1) + 1;
- const u32 surface_height = static_cast<u32>(config.surface_height_minus1) + 1;
- const u32 width = std::min(surface_width, static_cast<u32>(frame->width));
- const u32 height = std::min(surface_height, static_cast<u32>(frame->height));
- const u32 blk_kind = static_cast<u32>(config.block_linear_kind);
- if (blk_kind != 0) {
- // swizzle pitch linear to block linear
- const u32 block_height = static_cast<u32>(config.block_linear_height_log2);
- const auto size = Texture::CalculateSize(true, 4, width, height, 1, block_height, 0);
- luma_buffer.resize(size);
- Texture::SwizzleSubrect(width, height, width * 4, width, 4, luma_buffer.data(),
- converted_frame_buf_addr, block_height, 0, 0);
-
- gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(), size);
- } else {
- // send pitch linear frame
- const size_t linear_size = width * height * 4;
- gpu.MemoryManager().WriteBlock(output_surface_luma_address, converted_frame_buf_addr,
- linear_size);
- }
-}
-
-void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) {
- LOG_TRACE(Service_NVDRV, "Writing YUV420 Frame");
-
- const std::size_t surface_width = config.surface_width_minus1 + 1;
- const std::size_t surface_height = config.surface_height_minus1 + 1;
- const std::size_t aligned_width = (surface_width + 0xff) & ~0xffUL;
- // Use the minimum of surface/frame dimensions to avoid buffer overflow.
- const auto frame_width = std::min(surface_width, static_cast<size_t>(frame->width));
- const auto frame_height = std::min(surface_height, static_cast<size_t>(frame->height));
-
- const auto stride = static_cast<size_t>(frame->linesize[0]);
-
- luma_buffer.resize(aligned_width * surface_height);
- chroma_buffer.resize(aligned_width * surface_height / 2);
-
- // Populate luma buffer
- const u8* luma_src = frame->data[0];
- for (std::size_t y = 0; y < frame_height; ++y) {
- const std::size_t src = y * stride;
- const std::size_t dst = y * aligned_width;
- for (std::size_t x = 0; x < frame_width; ++x) {
- luma_buffer[dst + x] = luma_src[src + x];
- }
- }
- gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(),
- luma_buffer.size());
-
- // Chroma
- const std::size_t half_height = frame_height / 2;
- const auto half_stride = static_cast<size_t>(frame->linesize[1]);
-
- switch (frame->format) {
- case AV_PIX_FMT_YUV420P: {
- // Frame from FFmpeg software
- // Populate chroma buffer from both channels with interleaving.
- const std::size_t half_width = frame_width / 2;
- const u8* chroma_b_src = frame->data[1];
- const u8* chroma_r_src = frame->data[2];
- for (std::size_t y = 0; y < half_height; ++y) {
- const std::size_t src = y * half_stride;
- const std::size_t dst = y * aligned_width;
-
- for (std::size_t x = 0; x < half_width; ++x) {
- chroma_buffer[dst + x * 2] = chroma_b_src[src + x];
- chroma_buffer[dst + x * 2 + 1] = chroma_r_src[src + x];
- }
- }
- break;
- }
- case AV_PIX_FMT_NV12: {
- // Frame from VA-API hardware
- // This is already interleaved so just copy
- const u8* chroma_src = frame->data[1];
- for (std::size_t y = 0; y < half_height; ++y) {
- const std::size_t src = y * stride;
- const std::size_t dst = y * aligned_width;
- for (std::size_t x = 0; x < frame_width; ++x) {
- chroma_buffer[dst + x] = chroma_src[src + x];
- }
- }
- break;
- }
- default:
- UNREACHABLE();
- break;
- }
- gpu.MemoryManager().WriteBlock(output_surface_chroma_address, chroma_buffer.data(),
- chroma_buffer.size());
-}
-
-} // namespace Tegra
diff --git a/src/video_core/command_classes/vic.h b/src/video_core/command_classes/vic.h
deleted file mode 100644
index 6d4cdfd57..000000000
--- a/src/video_core/command_classes/vic.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <vector>
-#include "common/common_types.h"
-
-struct SwsContext;
-
-namespace Tegra {
-class GPU;
-class Nvdec;
-union VicConfig;
-
-class Vic {
-public:
- enum class Method : u32 {
- Execute = 0xc0,
- SetControlParams = 0x1c1,
- SetConfigStructOffset = 0x1c2,
- SetOutputSurfaceLumaOffset = 0x1c8,
- SetOutputSurfaceChromaOffset = 0x1c9,
- SetOutputSurfaceChromaUnusedOffset = 0x1ca
- };
-
- explicit Vic(GPU& gpu, std::shared_ptr<Nvdec> nvdec_processor);
-
- ~Vic();
-
- /// Write to the device state.
- void ProcessMethod(Method method, u32 argument);
-
-private:
- void Execute();
-
- void WriteRGBFrame(const AVFrame* frame, const VicConfig& config);
-
- void WriteYUVFrame(const AVFrame* frame, const VicConfig& config);
-
- GPU& gpu;
- std::shared_ptr<Tegra::Nvdec> nvdec_processor;
-
- /// Avoid reallocation of the following buffers every frame, as their
- /// size does not change during a stream
- using AVMallocPtr = std::unique_ptr<u8, decltype(&av_free)>;
- AVMallocPtr converted_frame_buffer;
- std::vector<u8> luma_buffer;
- std::vector<u8> chroma_buffer;
-
- GPUVAddr config_struct_address{};
- GPUVAddr output_surface_luma_address{};
- GPUVAddr output_surface_chroma_address{};
-
- SwsContext* scaler_ctx{};
- s32 scaler_width{};
- s32 scaler_height{};
-};
-
-} // namespace Tegra
diff --git a/src/video_core/compatible_formats.cpp b/src/video_core/compatible_formats.cpp
index 8317d0636..4e75f33ca 100644
--- a/src/video_core/compatible_formats.cpp
+++ b/src/video_core/compatible_formats.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <cstddef>
@@ -132,9 +131,12 @@ constexpr std::array VIEW_CLASS_ASTC_8x8_RGBA{
// PixelFormat::ASTC_2D_10X5_SRGB
// Missing formats:
-// PixelFormat::ASTC_2D_10X6_UNORM
// PixelFormat::ASTC_2D_10X6_SRGB
+constexpr std::array VIEW_CLASS_ASTC_10x6_RGBA{
+ PixelFormat::ASTC_2D_10X6_UNORM,
+};
+
constexpr std::array VIEW_CLASS_ASTC_10x8_RGBA{
PixelFormat::ASTC_2D_10X8_UNORM,
PixelFormat::ASTC_2D_10X8_SRGB,
@@ -227,6 +229,7 @@ constexpr Table MakeViewTable() {
EnableRange(view, VIEW_CLASS_ASTC_6x6_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_8x5_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_8x8_RGBA);
+ EnableRange(view, VIEW_CLASS_ASTC_10x6_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_10x8_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_10x10_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_12x12_RGBA);
diff --git a/src/video_core/compatible_formats.h b/src/video_core/compatible_formats.h
index 55745e042..a7ce2c198 100644
--- a/src/video_core/compatible_formats.h
+++ b/src/video_core/compatible_formats.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/control/channel_state.cpp b/src/video_core/control/channel_state.cpp
new file mode 100644
index 000000000..cdecc3a91
--- /dev/null
+++ b/src/video_core/control/channel_state.cpp
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/assert.h"
+#include "video_core/control/channel_state.h"
+#include "video_core/dma_pusher.h"
+#include "video_core/engines/fermi_2d.h"
+#include "video_core/engines/kepler_compute.h"
+#include "video_core/engines/kepler_memory.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/engines/maxwell_dma.h"
+#include "video_core/engines/puller.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra::Control {
+
+ChannelState::ChannelState(s32 bind_id_) : bind_id{bind_id_}, initialized{} {}
+
+void ChannelState::Init(Core::System& system, GPU& gpu) {
+ ASSERT(memory_manager);
+ dma_pusher = std::make_unique<Tegra::DmaPusher>(system, gpu, *memory_manager, *this);
+ maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, *memory_manager);
+ fermi_2d = std::make_unique<Engines::Fermi2D>();
+ kepler_compute = std::make_unique<Engines::KeplerCompute>(system, *memory_manager);
+ maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager);
+ kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager);
+ initialized = true;
+}
+
+void ChannelState::BindRasterizer(VideoCore::RasterizerInterface* rasterizer) {
+ dma_pusher->BindRasterizer(rasterizer);
+ memory_manager->BindRasterizer(rasterizer);
+ maxwell_3d->BindRasterizer(rasterizer);
+ fermi_2d->BindRasterizer(rasterizer);
+ kepler_memory->BindRasterizer(rasterizer);
+ kepler_compute->BindRasterizer(rasterizer);
+ maxwell_dma->BindRasterizer(rasterizer);
+}
+
+} // namespace Tegra::Control
diff --git a/src/video_core/control/channel_state.h b/src/video_core/control/channel_state.h
new file mode 100644
index 000000000..3a7b9872c
--- /dev/null
+++ b/src/video_core/control/channel_state.h
@@ -0,0 +1,68 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_types.h"
+
+namespace Core {
+class System;
+}
+
+namespace VideoCore {
+class RasterizerInterface;
+}
+
+namespace Tegra {
+
+class GPU;
+
+namespace Engines {
+class Puller;
+class Fermi2D;
+class Maxwell3D;
+class MaxwellDMA;
+class KeplerCompute;
+class KeplerMemory;
+} // namespace Engines
+
+class MemoryManager;
+class DmaPusher;
+
+namespace Control {
+
+struct ChannelState {
+ explicit ChannelState(s32 bind_id);
+ ChannelState(const ChannelState& state) = delete;
+ ChannelState& operator=(const ChannelState&) = delete;
+ ChannelState(ChannelState&& other) noexcept = default;
+ ChannelState& operator=(ChannelState&& other) noexcept = default;
+
+ void Init(Core::System& system, GPU& gpu);
+
+ void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
+
+ s32 bind_id = -1;
+ /// 3D engine
+ std::unique_ptr<Engines::Maxwell3D> maxwell_3d;
+ /// 2D engine
+ std::unique_ptr<Engines::Fermi2D> fermi_2d;
+ /// Compute engine
+ std::unique_ptr<Engines::KeplerCompute> kepler_compute;
+ /// DMA engine
+ std::unique_ptr<Engines::MaxwellDMA> maxwell_dma;
+ /// Inline memory engine
+ std::unique_ptr<Engines::KeplerMemory> kepler_memory;
+
+ std::shared_ptr<MemoryManager> memory_manager;
+
+ std::unique_ptr<DmaPusher> dma_pusher;
+
+ bool initialized{};
+};
+
+} // namespace Control
+
+} // namespace Tegra
diff --git a/src/video_core/control/channel_state_cache.cpp b/src/video_core/control/channel_state_cache.cpp
new file mode 100644
index 000000000..4ebeb6356
--- /dev/null
+++ b/src/video_core/control/channel_state_cache.cpp
@@ -0,0 +1,14 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "video_core/control/channel_state_cache.inc"
+
+namespace VideoCommon {
+
+ChannelInfo::ChannelInfo(Tegra::Control::ChannelState& channel_state)
+ : maxwell3d{*channel_state.maxwell_3d}, kepler_compute{*channel_state.kepler_compute},
+ gpu_memory{*channel_state.memory_manager} {}
+
+template class VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo>;
+
+} // namespace VideoCommon
diff --git a/src/video_core/control/channel_state_cache.h b/src/video_core/control/channel_state_cache.h
new file mode 100644
index 000000000..584a0c26c
--- /dev/null
+++ b/src/video_core/control/channel_state_cache.h
@@ -0,0 +1,101 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <deque>
+#include <limits>
+#include <mutex>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Tegra {
+
+namespace Engines {
+class Maxwell3D;
+class KeplerCompute;
+} // namespace Engines
+
+class MemoryManager;
+
+namespace Control {
+struct ChannelState;
+}
+
+} // namespace Tegra
+
+namespace VideoCommon {
+
+class ChannelInfo {
+public:
+ ChannelInfo() = delete;
+ explicit ChannelInfo(Tegra::Control::ChannelState& state);
+ ChannelInfo(const ChannelInfo& state) = delete;
+ ChannelInfo& operator=(const ChannelInfo&) = delete;
+ ChannelInfo(ChannelInfo&& other) = default;
+ ChannelInfo& operator=(ChannelInfo&& other) = default;
+
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
+ Tegra::MemoryManager& gpu_memory;
+};
+
+template <class P>
+class ChannelSetupCaches {
+public:
+ /// Operations for seting the channel of execution.
+ virtual ~ChannelSetupCaches();
+
+ /// Create channel state.
+ virtual void CreateChannel(Tegra::Control::ChannelState& channel);
+
+ /// Bind a channel for execution.
+ void BindToChannel(s32 id);
+
+ /// Erase channel's state.
+ void EraseChannel(s32 id);
+
+ Tegra::MemoryManager* GetFromID(size_t id) const {
+ std::unique_lock<std::mutex> lk(config_mutex);
+ const auto ref = address_spaces.find(id);
+ return ref->second.gpu_memory;
+ }
+
+ std::optional<size_t> getStorageID(size_t id) const {
+ std::unique_lock<std::mutex> lk(config_mutex);
+ const auto ref = address_spaces.find(id);
+ if (ref == address_spaces.end()) {
+ return std::nullopt;
+ }
+ return ref->second.storage_id;
+ }
+
+protected:
+ static constexpr size_t UNSET_CHANNEL{std::numeric_limits<size_t>::max()};
+
+ P* channel_state;
+ size_t current_channel_id{UNSET_CHANNEL};
+ size_t current_address_space{};
+ Tegra::Engines::Maxwell3D* maxwell3d;
+ Tegra::Engines::KeplerCompute* kepler_compute;
+ Tegra::MemoryManager* gpu_memory;
+
+ std::deque<P> channel_storage;
+ std::deque<size_t> free_channel_ids;
+ std::unordered_map<s32, size_t> channel_map;
+ std::vector<size_t> active_channel_ids;
+ struct AddresSpaceRef {
+ size_t ref_count;
+ size_t storage_id;
+ Tegra::MemoryManager* gpu_memory;
+ };
+ std::unordered_map<size_t, AddresSpaceRef> address_spaces;
+ mutable std::mutex config_mutex;
+
+ virtual void OnGPUASRegister([[maybe_unused]] size_t map_id) {}
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/control/channel_state_cache.inc b/src/video_core/control/channel_state_cache.inc
new file mode 100644
index 000000000..460313893
--- /dev/null
+++ b/src/video_core/control/channel_state_cache.inc
@@ -0,0 +1,86 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <algorithm>
+
+#include "video_core/control/channel_state.h"
+#include "video_core/control/channel_state_cache.h"
+#include "video_core/engines/kepler_compute.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/memory_manager.h"
+
+namespace VideoCommon {
+
+template <class P>
+ChannelSetupCaches<P>::~ChannelSetupCaches() = default;
+
+template <class P>
+void ChannelSetupCaches<P>::CreateChannel(struct Tegra::Control::ChannelState& channel) {
+ std::unique_lock<std::mutex> lk(config_mutex);
+ ASSERT(channel_map.find(channel.bind_id) == channel_map.end() && channel.bind_id >= 0);
+ auto new_id = [this, &channel]() {
+ if (!free_channel_ids.empty()) {
+ auto id = free_channel_ids.front();
+ free_channel_ids.pop_front();
+ new (&channel_storage[id]) P(channel);
+ return id;
+ }
+ channel_storage.emplace_back(channel);
+ return channel_storage.size() - 1;
+ }();
+ channel_map.emplace(channel.bind_id, new_id);
+ if (current_channel_id != UNSET_CHANNEL) {
+ channel_state = &channel_storage[current_channel_id];
+ }
+ active_channel_ids.push_back(new_id);
+ auto as_it = address_spaces.find(channel.memory_manager->GetID());
+ if (as_it != address_spaces.end()) {
+ as_it->second.ref_count++;
+ return;
+ }
+ AddresSpaceRef new_gpu_mem_ref{
+ .ref_count = 1,
+ .storage_id = address_spaces.size(),
+ .gpu_memory = channel.memory_manager.get(),
+ };
+ address_spaces.emplace(channel.memory_manager->GetID(), new_gpu_mem_ref);
+ OnGPUASRegister(channel.memory_manager->GetID());
+}
+
+/// Bind a channel for execution.
+template <class P>
+void ChannelSetupCaches<P>::BindToChannel(s32 id) {
+ std::unique_lock<std::mutex> lk(config_mutex);
+ auto it = channel_map.find(id);
+ ASSERT(it != channel_map.end() && id >= 0);
+ current_channel_id = it->second;
+ channel_state = &channel_storage[current_channel_id];
+ maxwell3d = &channel_state->maxwell3d;
+ kepler_compute = &channel_state->kepler_compute;
+ gpu_memory = &channel_state->gpu_memory;
+ current_address_space = gpu_memory->GetID();
+}
+
+/// Erase channel's channel_state.
+template <class P>
+void ChannelSetupCaches<P>::EraseChannel(s32 id) {
+ std::unique_lock<std::mutex> lk(config_mutex);
+ const auto it = channel_map.find(id);
+ ASSERT(it != channel_map.end() && id >= 0);
+ const auto this_id = it->second;
+ free_channel_ids.push_back(this_id);
+ channel_map.erase(it);
+ if (this_id == current_channel_id) {
+ current_channel_id = UNSET_CHANNEL;
+ channel_state = nullptr;
+ maxwell3d = nullptr;
+ kepler_compute = nullptr;
+ gpu_memory = nullptr;
+ } else if (current_channel_id != UNSET_CHANNEL) {
+ channel_state = &channel_storage[current_channel_id];
+ }
+ active_channel_ids.erase(
+ std::find(active_channel_ids.begin(), active_channel_ids.end(), this_id));
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/control/scheduler.cpp b/src/video_core/control/scheduler.cpp
new file mode 100644
index 000000000..f7cbe204e
--- /dev/null
+++ b/src/video_core/control/scheduler.cpp
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <memory>
+
+#include "common/assert.h"
+#include "video_core/control/channel_state.h"
+#include "video_core/control/scheduler.h"
+#include "video_core/gpu.h"
+
+namespace Tegra::Control {
+Scheduler::Scheduler(GPU& gpu_) : gpu{gpu_} {}
+
+Scheduler::~Scheduler() = default;
+
+void Scheduler::Push(s32 channel, CommandList&& entries) {
+ std::unique_lock lk(scheduling_guard);
+ auto it = channels.find(channel);
+ ASSERT(it != channels.end());
+ auto channel_state = it->second;
+ gpu.BindChannel(channel_state->bind_id);
+ channel_state->dma_pusher->Push(std::move(entries));
+ channel_state->dma_pusher->DispatchCalls();
+}
+
+void Scheduler::DeclareChannel(std::shared_ptr<ChannelState> new_channel) {
+ s32 channel = new_channel->bind_id;
+ std::unique_lock lk(scheduling_guard);
+ channels.emplace(channel, new_channel);
+}
+
+} // namespace Tegra::Control
diff --git a/src/video_core/control/scheduler.h b/src/video_core/control/scheduler.h
new file mode 100644
index 000000000..44addf61c
--- /dev/null
+++ b/src/video_core/control/scheduler.h
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+
+#include "video_core/dma_pusher.h"
+
+namespace Tegra {
+
+class GPU;
+
+namespace Control {
+
+struct ChannelState;
+
+class Scheduler {
+public:
+ explicit Scheduler(GPU& gpu_);
+ ~Scheduler();
+
+ void Push(s32 channel, CommandList&& entries);
+
+ void DeclareChannel(std::shared_ptr<ChannelState> new_channel);
+
+private:
+ std::unordered_map<s32, std::shared_ptr<ChannelState>> channels;
+ std::mutex scheduling_guard;
+ GPU& gpu;
+};
+
+} // namespace Control
+
+} // namespace Tegra
diff --git a/src/video_core/delayed_destruction_ring.h b/src/video_core/delayed_destruction_ring.h
index 4f1d29c04..d13ee622b 100644
--- a/src/video_core/delayed_destruction_ring.h
+++ b/src/video_core/delayed_destruction_ring.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/dirty_flags.cpp b/src/video_core/dirty_flags.cpp
index b1be065c3..9dc4341f0 100644
--- a/src/video_core/dirty_flags.cpp
+++ b/src/video_core/dirty_flags.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <cstddef>
diff --git a/src/video_core/dirty_flags.h b/src/video_core/dirty_flags.h
index d63ad5a35..736082d83 100644
--- a/src/video_core/dirty_flags.h
+++ b/src/video_core/dirty_flags.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp
index 8d28bd884..9835e3ac1 100644
--- a/src/video_core/dma_pusher.cpp
+++ b/src/video_core/dma_pusher.cpp
@@ -1,12 +1,10 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/cityhash.h"
#include "common/microprofile.h"
#include "common/settings.h"
#include "core/core.h"
-#include "core/memory.h"
#include "video_core/dma_pusher.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/gpu.h"
@@ -14,7 +12,10 @@
namespace Tegra {
-DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_) : gpu{gpu_}, system{system_} {}
+DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
+ Control::ChannelState& channel_state_)
+ : gpu{gpu_}, system{system_}, memory_manager{memory_manager_}, puller{gpu_, memory_manager_,
+ *this, channel_state_} {}
DmaPusher::~DmaPusher() = default;
@@ -23,8 +24,6 @@ MICROPROFILE_DEFINE(DispatchCalls, "GPU", "Execute command buffer", MP_RGB(128,
void DmaPusher::DispatchCalls() {
MICROPROFILE_SCOPE(DispatchCalls);
- gpu.SyncGuestHost();
-
dma_pushbuffer_subindex = 0;
dma_state.is_last_call = true;
@@ -35,7 +34,6 @@ void DmaPusher::DispatchCalls() {
}
}
gpu.FlushCommands();
- gpu.SyncGuestHost();
gpu.OnCommandListEnd();
}
@@ -78,11 +76,11 @@ bool DmaPusher::Step() {
// Push buffer non-empty, read a word
command_headers.resize(command_list_header.size);
if (Settings::IsGPULevelHigh()) {
- gpu.MemoryManager().ReadBlock(dma_get, command_headers.data(),
- command_list_header.size * sizeof(u32));
+ memory_manager.ReadBlock(dma_get, command_headers.data(),
+ command_list_header.size * sizeof(u32));
} else {
- gpu.MemoryManager().ReadBlockUnsafe(dma_get, command_headers.data(),
- command_list_header.size * sizeof(u32));
+ memory_manager.ReadBlockUnsafe(dma_get, command_headers.data(),
+ command_list_header.size * sizeof(u32));
}
}
for (std::size_t index = 0; index < command_headers.size();) {
@@ -156,7 +154,7 @@ void DmaPusher::SetState(const CommandHeader& command_header) {
void DmaPusher::CallMethod(u32 argument) const {
if (dma_state.method < non_puller_methods) {
- gpu.CallMethod(GPU::MethodCall{
+ puller.CallPullerMethod(Engines::Puller::MethodCall{
dma_state.method,
argument,
dma_state.subchannel,
@@ -170,12 +168,16 @@ void DmaPusher::CallMethod(u32 argument) const {
void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) const {
if (dma_state.method < non_puller_methods) {
- gpu.CallMultiMethod(dma_state.method, dma_state.subchannel, base_start, num_methods,
- dma_state.method_count);
+ puller.CallMultiMethod(dma_state.method, dma_state.subchannel, base_start, num_methods,
+ dma_state.method_count);
} else {
subchannels[dma_state.subchannel]->CallMultiMethod(dma_state.method, base_start,
num_methods, dma_state.method_count);
}
}
+void DmaPusher::BindRasterizer(VideoCore::RasterizerInterface* rasterizer) {
+ puller.BindRasterizer(rasterizer);
+}
+
} // namespace Tegra
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h
index 19f286fa7..938f0f11c 100644
--- a/src/video_core/dma_pusher.h
+++ b/src/video_core/dma_pusher.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -11,6 +10,7 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "video_core/engines/engine_interface.h"
+#include "video_core/engines/puller.h"
namespace Core {
class System;
@@ -18,7 +18,12 @@ class System;
namespace Tegra {
+namespace Control {
+struct ChannelState;
+}
+
class GPU;
+class MemoryManager;
enum class SubmissionMode : u32 {
IncreasingOld = 0,
@@ -32,24 +37,32 @@ enum class SubmissionMode : u32 {
// Note that, traditionally, methods are treated as 4-byte addressable locations, and hence
// their numbers are written down multiplied by 4 in Docs. Here we are not multiply by 4.
// So the values you see in docs might be multiplied by 4.
+// Register documentation:
+// https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/classes/host/cla26f.h
+//
+// Register Description (approx):
+// https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/manuals/volta/gv100/dev_pbdma.ref.txt
enum class BufferMethods : u32 {
BindObject = 0x0,
+ Illegal = 0x1,
Nop = 0x2,
SemaphoreAddressHigh = 0x4,
SemaphoreAddressLow = 0x5,
- SemaphoreSequence = 0x6,
- SemaphoreTrigger = 0x7,
- NotifyIntr = 0x8,
+ SemaphoreSequencePayload = 0x6,
+ SemaphoreOperation = 0x7,
+ NonStallInterrupt = 0x8,
WrcacheFlush = 0x9,
- Unk28 = 0xA,
- UnkCacheFlush = 0xB,
+ MemOpA = 0xA,
+ MemOpB = 0xB,
+ MemOpC = 0xC,
+ MemOpD = 0xD,
RefCnt = 0x14,
SemaphoreAcquire = 0x1A,
SemaphoreRelease = 0x1B,
- FenceValue = 0x1C,
- FenceAction = 0x1D,
- WaitForInterrupt = 0x1E,
- Unk7c = 0x1F,
+ SyncpointPayload = 0x1C,
+ SyncpointOperation = 0x1D,
+ WaitForIdle = 0x1E,
+ CRCCheck = 0x1F,
Yield = 0x20,
NonPullerMethods = 0x40,
};
@@ -103,7 +116,8 @@ struct CommandList final {
*/
class DmaPusher final {
public:
- explicit DmaPusher(Core::System& system_, GPU& gpu_);
+ explicit DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
+ Control::ChannelState& channel_state_);
~DmaPusher();
void Push(CommandList&& entries) {
@@ -116,6 +130,8 @@ public:
subchannels[subchannel_id] = engine;
}
+ void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
+
private:
static constexpr u32 non_puller_methods = 0x40;
static constexpr u32 max_subchannels = 8;
@@ -149,6 +165,8 @@ private:
GPU& gpu;
Core::System& system;
+ MemoryManager& memory_manager;
+ mutable Engines::Puller puller;
};
} // namespace Tegra
diff --git a/src/video_core/engines/const_buffer_info.h b/src/video_core/engines/const_buffer_info.h
index d8f672462..abafcf090 100644
--- a/src/video_core/engines/const_buffer_info.h
+++ b/src/video_core/engines/const_buffer_info.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/engines/engine_interface.h b/src/video_core/engines/engine_interface.h
index c7ffd68c5..26cde8584 100644
--- a/src/video_core/engines/engine_interface.h
+++ b/src/video_core/engines/engine_interface.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/engines/engine_upload.cpp b/src/video_core/engines/engine_upload.cpp
index 351b110fe..a34819234 100644
--- a/src/video_core/engines/engine_upload.cpp
+++ b/src/video_core/engines/engine_upload.cpp
@@ -1,9 +1,9 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
+#include "common/algorithm.h"
#include "common/assert.h"
#include "video_core/engines/engine_upload.h"
#include "video_core/memory_manager.h"
@@ -35,21 +35,48 @@ void State::ProcessData(const u32 data, const bool is_last_call) {
if (!is_last_call) {
return;
}
+ ProcessData(inner_buffer);
+}
+
+void State::ProcessData(const u32* data, size_t num_data) {
+ std::span<const u8> read_buffer(reinterpret_cast<const u8*>(data), num_data * sizeof(u32));
+ ProcessData(read_buffer);
+}
+
+void State::ProcessData(std::span<const u8> read_buffer) {
const GPUVAddr address{regs.dest.Address()};
if (is_linear) {
- rasterizer->AccelerateInlineToMemory(address, copy_size, inner_buffer);
+ if (regs.line_count == 1) {
+ rasterizer->AccelerateInlineToMemory(address, copy_size, read_buffer);
+ } else {
+ for (u32 line = 0; line < regs.line_count; ++line) {
+ const GPUVAddr dest_line = address + static_cast<size_t>(line) * regs.dest.pitch;
+ memory_manager.WriteBlockUnsafe(
+ dest_line, read_buffer.data() + static_cast<size_t>(line) * regs.line_length_in,
+ regs.line_length_in);
+ }
+ memory_manager.InvalidateRegion(address, regs.dest.pitch * regs.line_count);
+ }
} else {
- UNIMPLEMENTED_IF(regs.dest.z != 0);
- UNIMPLEMENTED_IF(regs.dest.depth != 1);
- UNIMPLEMENTED_IF(regs.dest.BlockWidth() != 0);
- UNIMPLEMENTED_IF(regs.dest.BlockDepth() != 0);
+ u32 width = regs.dest.width;
+ u32 x_elements = regs.line_length_in;
+ u32 x_offset = regs.dest.x;
+ const u32 bpp_shift = Common::FoldRight(
+ 4U, [](u32 x, u32 y) { return std::min(x, static_cast<u32>(std::countr_zero(y))); },
+ width, x_elements, x_offset, static_cast<u32>(address));
+ width >>= bpp_shift;
+ x_elements >>= bpp_shift;
+ x_offset >>= bpp_shift;
+ const u32 bytes_per_pixel = 1U << bpp_shift;
const std::size_t dst_size = Tegra::Texture::CalculateSize(
- true, 1, regs.dest.width, regs.dest.height, 1, regs.dest.BlockHeight(), 0);
+ true, bytes_per_pixel, width, regs.dest.height, regs.dest.depth,
+ regs.dest.BlockHeight(), regs.dest.BlockDepth());
tmp_buffer.resize(dst_size);
memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);
- Tegra::Texture::SwizzleKepler(regs.dest.width, regs.dest.height, regs.dest.x, regs.dest.y,
- regs.dest.BlockHeight(), copy_size, inner_buffer.data(),
- tmp_buffer.data());
+ Tegra::Texture::SwizzleSubrect(tmp_buffer, read_buffer, bytes_per_pixel, width,
+ regs.dest.height, regs.dest.depth, x_offset, regs.dest.y,
+ x_elements, regs.line_count, regs.dest.BlockHeight(),
+ regs.dest.BlockDepth(), regs.line_length_in);
memory_manager.WriteBlock(address, tmp_buffer.data(), dst_size);
}
}
diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h
index c9c5ec8c3..f08f6e36a 100644
--- a/src/video_core/engines/engine_upload.h
+++ b/src/video_core/engines/engine_upload.h
@@ -1,9 +1,9 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include <span>
#include <vector>
#include "common/bit_field.h"
#include "common/common_types.h"
@@ -34,7 +34,7 @@ struct Registers {
u32 width;
u32 height;
u32 depth;
- u32 z;
+ u32 layer;
u32 x;
u32 y;
@@ -63,11 +63,14 @@ public:
void ProcessExec(bool is_linear_);
void ProcessData(u32 data, bool is_last_call);
+ void ProcessData(const u32* data, size_t num_data);
/// Binds a rasterizer to this engine.
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
private:
+ void ProcessData(std::span<const u8> read_buffer);
+
u32 write_offset = 0;
u32 copy_size = 0;
std::vector<u8> inner_buffer;
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index f26530ede..453e0fb01 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/logging/log.h"
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
index d76c5ed56..1229aa35b 100644
--- a/src/video_core/engines/fermi_2d.h
+++ b/src/video_core/engines/fermi_2d.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -9,7 +8,6 @@
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
-#include "common/math_util.h"
#include "video_core/engines/engine_interface.h"
#include "video_core/gpu.h"
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index 5a1c12076..7c50bdbe0 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <bitset>
#include "common/assert.h"
@@ -10,7 +9,6 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
-#include "video_core/renderer_base.h"
#include "video_core/textures/decoders.h"
namespace Tegra::Engines {
@@ -38,8 +36,6 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal
}
case KEPLER_COMPUTE_REG_INDEX(data_upload): {
upload_state.ProcessData(method_argument, is_last_call);
- if (is_last_call) {
- }
break;
}
case KEPLER_COMPUTE_REG_INDEX(launch):
@@ -52,8 +48,15 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal
void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) {
- for (std::size_t i = 0; i < amount; i++) {
- CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+ switch (method) {
+ case KEPLER_COMPUTE_REG_INDEX(data_upload):
+ upload_state.ProcessData(base_start, static_cast<size_t>(amount));
+ return;
+ default:
+ for (std::size_t i = 0; i < amount; i++) {
+ CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+ }
+ break;
}
}
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index f8b8d06ac..aab309ecc 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -12,7 +11,6 @@
#include "common/common_types.h"
#include "video_core/engines/engine_interface.h"
#include "video_core/engines/engine_upload.h"
-#include "video_core/gpu.h"
#include "video_core/textures/texture.h"
namespace Core {
diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp
index 8aed16caa..a3fbab1e5 100644
--- a/src/video_core/engines/kepler_memory.cpp
+++ b/src/video_core/engines/kepler_memory.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/logging/log.h"
@@ -9,8 +8,6 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
-#include "video_core/renderer_base.h"
-#include "video_core/textures/decoders.h"
namespace Tegra::Engines {
@@ -36,8 +33,6 @@ void KeplerMemory::CallMethod(u32 method, u32 method_argument, bool is_last_call
}
case KEPLERMEMORY_REG_INDEX(data): {
upload_state.ProcessData(method_argument, is_last_call);
- if (is_last_call) {
- }
break;
}
}
@@ -45,8 +40,15 @@ void KeplerMemory::CallMethod(u32 method, u32 method_argument, bool is_last_call
void KeplerMemory::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) {
- for (std::size_t i = 0; i < amount; i++) {
- CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+ switch (method) {
+ case KEPLERMEMORY_REG_INDEX(data):
+ upload_state.ProcessData(base_start, static_cast<size_t>(amount));
+ return;
+ default:
+ for (std::size_t i = 0; i < amount; i++) {
+ CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+ }
+ break;
}
}
diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h
index 949e2fae1..5fe7489f0 100644
--- a/src/video_core/engines/kepler_memory.h
+++ b/src/video_core/engines/kepler_memory.h
@@ -1,18 +1,15 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
-#include <vector>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "video_core/engines/engine_interface.h"
#include "video_core/engines/engine_upload.h"
-#include "video_core/gpu.h"
namespace Core {
class System;
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 5d6d217bb..3c6e44a25 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -1,12 +1,12 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <optional>
#include "common/assert.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
@@ -173,6 +173,8 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
case MAXWELL3D_REG_INDEX(shadow_ram_control):
shadow_state.shadow_ram_control = static_cast<Regs::ShadowRamControl>(nonshadow_argument);
return;
+ case MAXWELL3D_REG_INDEX(macros.upload_address):
+ return macro_engine->ClearCode(regs.macros.upload_address);
case MAXWELL3D_REG_INDEX(macros.data):
return macro_engine->AddCode(regs.macros.upload_address, argument);
case MAXWELL3D_REG_INDEX(macros.bind):
@@ -195,7 +197,7 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 13:
case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 14:
case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 15:
- return StartCBData(method);
+ return ProcessCBData(argument);
case MAXWELL3D_REG_INDEX(cb_bind[0]):
return ProcessCBBind(0);
case MAXWELL3D_REG_INDEX(cb_bind[1]):
@@ -208,6 +210,21 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
return ProcessCBBind(4);
case MAXWELL3D_REG_INDEX(draw.vertex_end_gl):
return DrawArrays();
+ case MAXWELL3D_REG_INDEX(small_index):
+ regs.index_array.count = regs.small_index.count;
+ 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;
+ // a macro calls this one over and over, should it increase instancing?
+ // Used by Hades and likely other Vulkan games.
+ return DrawArrays();
+ case MAXWELL3D_REG_INDEX(topology_override):
+ use_topology_override = true;
+ return;
case MAXWELL3D_REG_INDEX(clear_buffers):
return ProcessClearBuffers();
case MAXWELL3D_REG_INDEX(query.query_get):
@@ -222,11 +239,12 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
return upload_state.ProcessExec(regs.exec_upload.linear != 0);
case MAXWELL3D_REG_INDEX(data_upload):
upload_state.ProcessData(argument, is_last_call);
- if (is_last_call) {
- }
return;
case MAXWELL3D_REG_INDEX(fragment_barrier):
return rasterizer->FragmentBarrier();
+ case MAXWELL3D_REG_INDEX(invalidate_texture_data_cache):
+ rasterizer->InvalidateGPUCache();
+ return rasterizer->WaitForIdle();
case MAXWELL3D_REG_INDEX(tiled_cache_barrier):
return rasterizer->TiledCacheBarrier();
}
@@ -248,14 +266,6 @@ void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters)
}
void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
- if (method == cb_data_state.current) {
- regs.reg_array[method] = method_argument;
- ProcessCBData(method_argument);
- return;
- } else if (cb_data_state.current != null_cb_data) {
- FinishCBData();
- }
-
// It is an error to write to a register other than the current macro's ARG register before it
// has finished execution.
if (executing_macro != 0) {
@@ -302,8 +312,11 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 13:
case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 14:
case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 15:
- ProcessCBMultiData(method, base_start, amount);
+ ProcessCBMultiData(base_start, amount);
break;
+ case MAXWELL3D_REG_INDEX(data_upload):
+ upload_state.ProcessData(base_start, static_cast<size_t>(amount));
+ return;
default:
for (std::size_t i = 0; i < amount; i++) {
CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
@@ -360,6 +373,35 @@ void Maxwell3D::CallMethodFromMME(u32 method, u32 method_argument) {
}
}
+void Maxwell3D::ProcessTopologyOverride() {
+ using PrimitiveTopology = Maxwell3D::Regs::PrimitiveTopology;
+ using PrimitiveTopologyOverride = Maxwell3D::Regs::PrimitiveTopologyOverride;
+
+ PrimitiveTopology topology{};
+
+ switch (regs.topology_override) {
+ case PrimitiveTopologyOverride::None:
+ topology = regs.draw.topology;
+ break;
+ case PrimitiveTopologyOverride::Points:
+ topology = PrimitiveTopology::Points;
+ break;
+ case PrimitiveTopologyOverride::Lines:
+ topology = PrimitiveTopology::Lines;
+ break;
+ case PrimitiveTopologyOverride::LineStrip:
+ topology = PrimitiveTopology::LineStrip;
+ break;
+ default:
+ topology = static_cast<PrimitiveTopology>(regs.topology_override);
+ break;
+ }
+
+ if (use_topology_override) {
+ regs.draw.topology.Assign(topology);
+ }
+}
+
void Maxwell3D::FlushMMEInlineDraw() {
LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(),
regs.vertex_buffer.count);
@@ -370,6 +412,8 @@ void Maxwell3D::FlushMMEInlineDraw() {
ASSERT_MSG(!regs.draw.instance_next || !regs.draw.instance_cont,
"Illegal combination of instancing parameters");
+ ProcessTopologyOverride();
+
const bool is_indexed = mme_draw.current_mode == MMEDrawMode::Indexed;
if (ShouldExecute()) {
rasterizer->Draw(is_indexed, true);
@@ -409,18 +453,10 @@ void Maxwell3D::ProcessFirmwareCall4() {
}
void Maxwell3D::StampQueryResult(u64 payload, bool long_query) {
- struct LongQueryResult {
- u64_le value;
- u64_le timestamp;
- };
- static_assert(sizeof(LongQueryResult) == 16, "LongQueryResult has wrong size");
const GPUVAddr sequence_address{regs.query.QueryAddress()};
if (long_query) {
- // Write the 128-bit result structure in long mode. Note: We emulate an infinitely fast
- // GPU, this command may actually take a while to complete in real hardware due to GPU
- // wait queues.
- LongQueryResult query_result{payload, system.GPU().GetTicks()};
- memory_manager.WriteBlock(sequence_address, &query_result, sizeof(query_result));
+ memory_manager.Write<u64>(sequence_address + sizeof(u64), system.GPU().GetTicks());
+ memory_manager.Write<u64>(sequence_address, payload);
} else {
memory_manager.Write<u32>(sequence_address, static_cast<u32>(payload));
}
@@ -434,10 +470,25 @@ void Maxwell3D::ProcessQueryGet() {
switch (regs.query.query_get.operation) {
case Regs::QueryOperation::Release:
- if (regs.query.query_get.fence == 1) {
- rasterizer->SignalSemaphore(regs.query.QueryAddress(), regs.query.query_sequence);
+ if (regs.query.query_get.fence == 1 || regs.query.query_get.short_query != 0) {
+ const GPUVAddr sequence_address{regs.query.QueryAddress()};
+ const u32 payload = regs.query.query_sequence;
+ std::function<void()> operation([this, sequence_address, payload] {
+ memory_manager.Write<u32>(sequence_address, payload);
+ });
+ rasterizer->SignalFence(std::move(operation));
} else {
- StampQueryResult(regs.query.query_sequence, regs.query.query_get.short_query == 0);
+ struct LongQueryResult {
+ u64_le value;
+ u64_le timestamp;
+ };
+ const GPUVAddr sequence_address{regs.query.QueryAddress()};
+ const u32 payload = regs.query.query_sequence;
+ std::function<void()> operation([this, sequence_address, payload] {
+ memory_manager.Write<u64>(sequence_address + sizeof(u64), system.GPU().GetTicks());
+ memory_manager.Write<u64>(sequence_address, payload);
+ });
+ rasterizer->SyncOperation(std::move(operation));
}
break;
case Regs::QueryOperation::Acquire:
@@ -529,6 +580,8 @@ void Maxwell3D::DrawArrays() {
ASSERT_MSG(!regs.draw.instance_next || !regs.draw.instance_cont,
"Illegal combination of instancing parameters");
+ ProcessTopologyOverride();
+
if (regs.draw.instance_next) {
// Increment the current instance *before* drawing.
state.current_instance += 1;
@@ -555,8 +608,8 @@ void Maxwell3D::DrawArrays() {
std::optional<u64> Maxwell3D::GetQueryResult() {
switch (regs.query.query_get.select) {
- case Regs::QuerySelect::Zero:
- return 0;
+ case Regs::QuerySelect::Payload:
+ return regs.query.query_sequence;
case Regs::QuerySelect::SamplesPassed:
// Deferred.
rasterizer->Query(regs.query.QueryAddress(), QueryType::SamplesPassed,
@@ -587,46 +640,7 @@ void Maxwell3D::ProcessCBBind(size_t stage_index) {
rasterizer->BindGraphicsUniformBuffer(stage_index, bind_data.index, gpu_addr, size);
}
-void Maxwell3D::ProcessCBData(u32 value) {
- const u32 id = cb_data_state.id;
- cb_data_state.buffer[id][cb_data_state.counter] = value;
- // Increment the current buffer position.
- regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4;
- cb_data_state.counter++;
-}
-
-void Maxwell3D::StartCBData(u32 method) {
- constexpr u32 first_cb_data = MAXWELL3D_REG_INDEX(const_buffer.cb_data);
- cb_data_state.start_pos = regs.const_buffer.cb_pos;
- cb_data_state.id = method - first_cb_data;
- cb_data_state.current = method;
- cb_data_state.counter = 0;
- ProcessCBData(regs.const_buffer.cb_data[cb_data_state.id]);
-}
-
-void Maxwell3D::ProcessCBMultiData(u32 method, const u32* start_base, u32 amount) {
- if (cb_data_state.current != method) {
- if (cb_data_state.current != null_cb_data) {
- FinishCBData();
- }
- constexpr u32 first_cb_data = MAXWELL3D_REG_INDEX(const_buffer.cb_data);
- cb_data_state.start_pos = regs.const_buffer.cb_pos;
- cb_data_state.id = method - first_cb_data;
- cb_data_state.current = method;
- cb_data_state.counter = 0;
- }
- const std::size_t id = cb_data_state.id;
- const std::size_t size = amount;
- std::size_t i = 0;
- for (; i < size; i++) {
- cb_data_state.buffer[id][cb_data_state.counter] = start_base[i];
- cb_data_state.counter++;
- }
- // Increment the current buffer position.
- regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4 * amount;
-}
-
-void Maxwell3D::FinishCBData() {
+void Maxwell3D::ProcessCBMultiData(const u32* start_base, u32 amount) {
// Write the input value to the current const buffer at the current position.
const GPUVAddr buffer_address = regs.const_buffer.BufferAddress();
ASSERT(buffer_address != 0);
@@ -634,14 +648,16 @@ void Maxwell3D::FinishCBData() {
// Don't allow writing past the end of the buffer.
ASSERT(regs.const_buffer.cb_pos <= regs.const_buffer.cb_size);
- const GPUVAddr address{buffer_address + cb_data_state.start_pos};
- const std::size_t size = regs.const_buffer.cb_pos - cb_data_state.start_pos;
+ const GPUVAddr address{buffer_address + regs.const_buffer.cb_pos};
+ const size_t copy_size = amount * sizeof(u32);
+ memory_manager.WriteBlock(address, start_base, copy_size);
- const u32 id = cb_data_state.id;
- memory_manager.WriteBlock(address, cb_data_state.buffer[id].data(), size);
+ // Increment the current buffer position.
+ regs.const_buffer.cb_pos += static_cast<u32>(copy_size);
+}
- cb_data_state.id = null_cb_data;
- cb_data_state.current = null_cb_data;
+void Maxwell3D::ProcessCBData(u32 value) {
+ ProcessCBMultiData(&value, 1);
}
Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index dc9df6c8b..5f9eb208c 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,7 +9,6 @@
#include <limits>
#include <optional>
#include <type_traits>
-#include <unordered_map>
#include <vector>
#include "common/assert.h"
@@ -95,7 +93,7 @@ public:
};
enum class QuerySelect : u32 {
- Zero = 0,
+ Payload = 0,
TimeElapsed = 2,
TransformFeedbackPrimitivesGenerated = 11,
PrimitivesGenerated = 18,
@@ -204,7 +202,7 @@ public:
case Size::Size_11_11_10:
return 3;
default:
- UNREACHABLE();
+ ASSERT(false);
return 1;
}
}
@@ -240,7 +238,7 @@ public:
case Size::Size_11_11_10:
return 4;
default:
- UNREACHABLE();
+ ASSERT(false);
return 1;
}
}
@@ -276,7 +274,7 @@ public:
case Size::Size_11_11_10:
return "11_11_10";
default:
- UNREACHABLE();
+ ASSERT(false);
return {};
}
}
@@ -298,7 +296,7 @@ public:
case Type::Float:
return "FLOAT";
}
- UNREACHABLE();
+ ASSERT(false);
return {};
}
@@ -338,7 +336,7 @@ public:
case 3:
return {x3, y3};
default:
- UNREACHABLE();
+ ASSERT(false);
return {0, 0};
}
}
@@ -367,6 +365,22 @@ public:
Patches = 0xe,
};
+ // Constants as from NVC0_3D_UNK1970_D3D
+ // https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/gallium/drivers/nouveau/nvc0/nvc0_3d.xml.h#L1598
+ enum class PrimitiveTopologyOverride : u32 {
+ None = 0x0,
+ Points = 0x1,
+ Lines = 0x2,
+ LineStrip = 0x3,
+ Triangles = 0x4,
+ TriangleStrip = 0x5,
+ LinesAdjacency = 0xa,
+ LineStripAdjacency = 0xb,
+ TrianglesAdjacency = 0xc,
+ TriangleStripAdjacency = 0xd,
+ Patches = 0xe,
+ };
+
enum class IndexFormat : u32 {
UnsignedByte = 0x0,
UnsignedShort = 0x1,
@@ -1179,7 +1193,7 @@ public:
case IndexFormat::UnsignedInt:
return 4;
}
- UNREACHABLE();
+ ASSERT(false);
return 1;
}
@@ -1200,7 +1214,17 @@ public:
}
} index_array;
- INSERT_PADDING_WORDS_NOINIT(0x7);
+ union {
+ BitField<0, 16, u32> first;
+ BitField<16, 16, u32> count;
+ } small_index;
+
+ union {
+ BitField<0, 16, u32> first;
+ BitField<16, 16, u32> count;
+ } small_index_2;
+
+ INSERT_PADDING_WORDS_NOINIT(0x5);
INSERT_PADDING_WORDS_NOINIT(0x1F);
@@ -1244,7 +1268,11 @@ public:
BitField<11, 1, u32> depth_clamp_disabled;
} view_volume_clip_control;
- INSERT_PADDING_WORDS_NOINIT(0x1F);
+ INSERT_PADDING_WORDS_NOINIT(0xC);
+
+ PrimitiveTopologyOverride topology_override;
+
+ INSERT_PADDING_WORDS_NOINIT(0x12);
u32 depth_bounds_enable;
@@ -1520,10 +1548,8 @@ private:
void ProcessSyncPoint();
/// Handles a write to the CB_DATA[i] register.
- void StartCBData(u32 method);
void ProcessCBData(u32 value);
- void ProcessCBMultiData(u32 method, const u32* start_base, u32 amount);
- void FinishCBData();
+ void ProcessCBMultiData(const u32* start_base, u32 amount);
/// Handles a write to the CB_BIND register.
void ProcessCBBind(size_t stage_index);
@@ -1531,6 +1557,9 @@ private:
/// Handles a write to the VERTEX_END_GL register, triggering a draw.
void DrawArrays();
+ /// Handles use of topology overrides (e.g., to avoid using a topology assigned from a macro)
+ void ProcessTopologyOverride();
+
// Handles a instance drawcall from MME
void StepInstance(MMEDrawMode expected_mode, u32 count);
@@ -1555,20 +1584,10 @@ private:
/// Interpreter for the macro codes uploaded to the GPU.
std::unique_ptr<MacroEngine> macro_engine;
- static constexpr u32 null_cb_data = 0xFFFFFFFF;
- struct CBDataState {
- static constexpr size_t inline_size = 0x4000;
- std::array<std::array<u32, inline_size>, 16> buffer;
- u32 current{null_cb_data};
- u32 id{null_cb_data};
- u32 start_pos{};
- u32 counter{};
- };
- CBDataState cb_data_state;
-
Upload::State upload_state;
bool execute_on{true};
+ bool use_topology_override{false};
};
#define ASSERT_REG_POSITION(field_name, position) \
@@ -1685,6 +1704,7 @@ ASSERT_REG_POSITION(draw, 0x585);
ASSERT_REG_POSITION(primitive_restart, 0x591);
ASSERT_REG_POSITION(provoking_vertex_last, 0x5A1);
ASSERT_REG_POSITION(index_array, 0x5F2);
+ASSERT_REG_POSITION(small_index, 0x5F9);
ASSERT_REG_POSITION(polygon_offset_clamp, 0x61F);
ASSERT_REG_POSITION(instanced_arrays, 0x620);
ASSERT_REG_POSITION(vp_point_size, 0x644);
@@ -1694,6 +1714,7 @@ ASSERT_REG_POSITION(cull_face, 0x648);
ASSERT_REG_POSITION(pixel_center_integer, 0x649);
ASSERT_REG_POSITION(viewport_transform_enabled, 0x64B);
ASSERT_REG_POSITION(view_volume_clip_control, 0x64F);
+ASSERT_REG_POSITION(topology_override, 0x65C);
ASSERT_REG_POSITION(depth_bounds_enable, 0x66F);
ASSERT_REG_POSITION(logic_op, 0x671);
ASSERT_REG_POSITION(clear_buffers, 0x674);
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index 67388d980..3909d36c1 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -1,7 +1,7 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/algorithm.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
@@ -53,18 +53,15 @@ void MaxwellDMA::Launch() {
// TODO(Subv): Perform more research and implement all features of this engine.
const LaunchDMA& launch = regs.launch_dma;
- ASSERT(launch.semaphore_type == LaunchDMA::SemaphoreType::NONE);
ASSERT(launch.interrupt_type == LaunchDMA::InterruptType::NONE);
ASSERT(launch.data_transfer_type == LaunchDMA::DataTransferType::NON_PIPELINED);
- ASSERT(regs.dst_params.origin.x == 0);
- ASSERT(regs.dst_params.origin.y == 0);
const bool is_src_pitch = launch.src_memory_layout == LaunchDMA::MemoryLayout::PITCH;
const bool is_dst_pitch = launch.dst_memory_layout == LaunchDMA::MemoryLayout::PITCH;
if (!is_src_pitch && !is_dst_pitch) {
// If both the source and the destination are in block layout, assert.
- UNREACHABLE_MSG("Tiled->Tiled DMA transfers are not yet implemented");
+ UNIMPLEMENTED_MSG("Tiled->Tiled DMA transfers are not yet implemented");
return;
}
@@ -79,6 +76,7 @@ void MaxwellDMA::Launch() {
CopyPitchToBlockLinear();
}
}
+ ReleaseSemaphore();
}
void MaxwellDMA::CopyPitchToPitch() {
@@ -122,22 +120,40 @@ void MaxwellDMA::CopyPitchToPitch() {
void MaxwellDMA::CopyBlockLinearToPitch() {
UNIMPLEMENTED_IF(regs.src_params.block_size.width != 0);
- UNIMPLEMENTED_IF(regs.src_params.block_size.depth != 0);
UNIMPLEMENTED_IF(regs.src_params.layer != 0);
+ const bool is_remapping = regs.launch_dma.remap_enable != 0;
+
// Optimized path for micro copies.
const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count;
- if (dst_size < GOB_SIZE && regs.pitch_out <= GOB_SIZE_X &&
+ if (!is_remapping && dst_size < GOB_SIZE && regs.pitch_out <= GOB_SIZE_X &&
regs.src_params.height > GOB_SIZE_Y) {
FastCopyBlockLinearToPitch();
return;
}
// Deswizzle the input and copy it over.
- UNIMPLEMENTED_IF(regs.launch_dma.remap_enable != 0);
- const u32 bytes_per_pixel = regs.pitch_out / regs.line_length_in;
const Parameters& src_params = regs.src_params;
- const u32 width = src_params.width;
+
+ const u32 num_remap_components = regs.remap_const.num_dst_components_minus_one + 1;
+ const u32 remap_components_size = regs.remap_const.component_size_minus_one + 1;
+
+ const u32 base_bpp = !is_remapping ? 1U : num_remap_components * remap_components_size;
+
+ u32 width = src_params.width;
+ u32 x_elements = regs.line_length_in;
+ u32 x_offset = src_params.origin.x;
+ u32 bpp_shift = 0U;
+ if (!is_remapping) {
+ bpp_shift = Common::FoldRight(
+ 4U, [](u32 x, u32 y) { return std::min(x, static_cast<u32>(std::countr_zero(y))); },
+ width, x_elements, x_offset, static_cast<u32>(regs.offset_in));
+ width >>= bpp_shift;
+ x_elements >>= bpp_shift;
+ x_offset >>= bpp_shift;
+ }
+
+ const u32 bytes_per_pixel = base_bpp << bpp_shift;
const u32 height = src_params.height;
const u32 depth = src_params.depth;
const u32 block_height = src_params.block_size.height;
@@ -155,29 +171,45 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
- UnswizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_out, width, bytes_per_pixel,
- block_height, src_params.origin.x, src_params.origin.y, write_buffer.data(),
- read_buffer.data());
+ UnswizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, width, height, depth, x_offset,
+ src_params.origin.y, x_elements, regs.line_count, block_height, block_depth,
+ regs.pitch_out);
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
}
void MaxwellDMA::CopyPitchToBlockLinear() {
UNIMPLEMENTED_IF_MSG(regs.dst_params.block_size.width != 0, "Block width is not one");
- UNIMPLEMENTED_IF(regs.launch_dma.remap_enable != 0);
+ UNIMPLEMENTED_IF(regs.dst_params.layer != 0);
+
+ const bool is_remapping = regs.launch_dma.remap_enable != 0;
+ const u32 num_remap_components = regs.remap_const.num_dst_components_minus_one + 1;
+ const u32 remap_components_size = regs.remap_const.component_size_minus_one + 1;
const auto& dst_params = regs.dst_params;
- const u32 bytes_per_pixel = regs.pitch_in / regs.line_length_in;
- const u32 width = dst_params.width;
+
+ const u32 base_bpp = !is_remapping ? 1U : num_remap_components * remap_components_size;
+
+ u32 width = dst_params.width;
+ u32 x_elements = regs.line_length_in;
+ u32 x_offset = dst_params.origin.x;
+ u32 bpp_shift = 0U;
+ if (!is_remapping) {
+ bpp_shift = Common::FoldRight(
+ 4U, [](u32 x, u32 y) { return std::min(x, static_cast<u32>(std::countr_zero(y))); },
+ width, x_elements, x_offset, static_cast<u32>(regs.offset_out));
+ width >>= bpp_shift;
+ x_elements >>= bpp_shift;
+ x_offset >>= bpp_shift;
+ }
+
+ const u32 bytes_per_pixel = base_bpp << bpp_shift;
const u32 height = dst_params.height;
const u32 depth = dst_params.depth;
const u32 block_height = dst_params.block_size.height;
const u32 block_depth = dst_params.block_size.depth;
const size_t dst_size =
CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
- const size_t dst_layer_size =
- CalculateSize(true, bytes_per_pixel, width, height, 1, block_height, block_depth);
-
const size_t src_size = static_cast<size_t>(regs.pitch_in) * regs.line_count;
if (read_buffer.size() < src_size) {
@@ -187,31 +219,23 @@ void MaxwellDMA::CopyPitchToBlockLinear() {
write_buffer.resize(dst_size);
}
+ memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
if (Settings::IsGPULevelExtreme()) {
- memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
} else {
- memory_manager.ReadBlockUnsafe(regs.offset_in, read_buffer.data(), src_size);
memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
}
// If the input is linear and the output is tiled, swizzle the input and copy it over.
- if (regs.dst_params.block_size.depth > 0) {
- ASSERT(dst_params.layer == 0);
- SwizzleSliceToVoxel(regs.line_length_in, regs.line_count, regs.pitch_in, width, height,
- bytes_per_pixel, block_height, block_depth, dst_params.origin.x,
- dst_params.origin.y, write_buffer.data(), read_buffer.data());
- } else {
- SwizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_in, width, bytes_per_pixel,
- write_buffer.data() + dst_layer_size * dst_params.layer, read_buffer.data(),
- block_height, dst_params.origin.x, dst_params.origin.y);
- }
+ SwizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, width, height, depth, x_offset,
+ dst_params.origin.y, x_elements, regs.line_count, block_height, block_depth,
+ regs.pitch_in);
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
}
void MaxwellDMA::FastCopyBlockLinearToPitch() {
- const u32 bytes_per_pixel = regs.pitch_out / regs.line_length_in;
+ const u32 bytes_per_pixel = 1U;
const size_t src_size = GOB_SIZE;
const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count;
u32 pos_x = regs.src_params.origin.x;
@@ -237,11 +261,38 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {
memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
}
- UnswizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_out, regs.src_params.width,
- bytes_per_pixel, regs.src_params.block_size.height, pos_x, pos_y,
- write_buffer.data(), read_buffer.data());
+ UnswizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, regs.src_params.width,
+ regs.src_params.height, 1, pos_x, pos_y, regs.line_length_in, regs.line_count,
+ regs.src_params.block_size.height, regs.src_params.block_size.depth,
+ regs.pitch_out);
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
}
+void MaxwellDMA::ReleaseSemaphore() {
+ const auto type = regs.launch_dma.semaphore_type;
+ const GPUVAddr address = regs.semaphore.address;
+ const u32 payload = regs.semaphore.payload;
+ switch (type) {
+ case LaunchDMA::SemaphoreType::NONE:
+ break;
+ case LaunchDMA::SemaphoreType::RELEASE_ONE_WORD_SEMAPHORE: {
+ std::function<void()> operation(
+ [this, address, payload] { memory_manager.Write<u32>(address, payload); });
+ rasterizer->SignalFence(std::move(operation));
+ break;
+ }
+ case LaunchDMA::SemaphoreType::RELEASE_FOUR_WORD_SEMAPHORE: {
+ std::function<void()> operation([this, address, payload] {
+ memory_manager.Write<u64>(address + sizeof(u64), system.GPU().GetTicks());
+ memory_manager.Write<u64>(address, payload);
+ });
+ rasterizer->SignalFence(std::move(operation));
+ break;
+ }
+ default:
+ ASSERT_MSG(false, "Unknown semaphore type: {}", static_cast<u32>(type.Value()));
+ }
+}
+
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h
index a04514425..bc48320ce 100644
--- a/src/video_core/engines/maxwell_dma.h
+++ b/src/video_core/engines/maxwell_dma.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,10 +7,8 @@
#include <cstddef>
#include <vector>
#include "common/bit_field.h"
-#include "common/common_funcs.h"
#include "common/common_types.h"
#include "video_core/engines/engine_interface.h"
-#include "video_core/gpu.h"
namespace Core {
class System;
@@ -192,10 +189,16 @@ public:
BitField<4, 3, Swizzle> dst_y;
BitField<8, 3, Swizzle> dst_z;
BitField<12, 3, Swizzle> dst_w;
+ BitField<0, 12, u32> dst_components_raw;
BitField<16, 2, u32> component_size_minus_one;
BitField<20, 2, u32> num_src_components_minus_one;
BitField<24, 2, u32> num_dst_components_minus_one;
};
+
+ Swizzle GetComponent(size_t i) const {
+ const u32 raw = dst_components_raw;
+ return static_cast<Swizzle>((raw >> (i * 3)) & 0x7);
+ }
};
static_assert(sizeof(RemapConst) == 12);
@@ -224,6 +227,8 @@ private:
void FastCopyBlockLinearToPitch();
+ void ReleaseSemaphore();
+
Core::System& system;
MemoryManager& memory_manager;
diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp
new file mode 100644
index 000000000..cca890792
--- /dev/null
+++ b/src/video_core/engines/puller.cpp
@@ -0,0 +1,306 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "core/core.h"
+#include "video_core/control/channel_state.h"
+#include "video_core/dma_pusher.h"
+#include "video_core/engines/fermi_2d.h"
+#include "video_core/engines/kepler_compute.h"
+#include "video_core/engines/kepler_memory.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/engines/maxwell_dma.h"
+#include "video_core/engines/puller.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+#include "video_core/rasterizer_interface.h"
+
+namespace Tegra::Engines {
+
+Puller::Puller(GPU& gpu_, MemoryManager& memory_manager_, DmaPusher& dma_pusher_,
+ Control::ChannelState& channel_state_)
+ : gpu{gpu_}, memory_manager{memory_manager_}, dma_pusher{dma_pusher_}, channel_state{
+ channel_state_} {}
+
+Puller::~Puller() = default;
+
+void Puller::ProcessBindMethod(const MethodCall& method_call) {
+ // Bind the current subchannel to the desired engine id.
+ LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", method_call.subchannel,
+ method_call.argument);
+ const auto engine_id = static_cast<EngineID>(method_call.argument);
+ bound_engines[method_call.subchannel] = static_cast<EngineID>(engine_id);
+ switch (engine_id) {
+ case EngineID::FERMI_TWOD_A:
+ dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel);
+ break;
+ case EngineID::MAXWELL_B:
+ dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel);
+ break;
+ case EngineID::KEPLER_COMPUTE_B:
+ dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel);
+ break;
+ case EngineID::MAXWELL_DMA_COPY_A:
+ dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel);
+ break;
+ case EngineID::KEPLER_INLINE_TO_MEMORY_B:
+ dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
+ }
+}
+
+void Puller::ProcessFenceActionMethod() {
+ switch (regs.fence_action.op) {
+ case Puller::FenceOperation::Acquire:
+ // UNIMPLEMENTED_MSG("Channel Scheduling pending.");
+ // WaitFence(regs.fence_action.syncpoint_id, regs.fence_value);
+ rasterizer->ReleaseFences();
+ break;
+ case Puller::FenceOperation::Increment:
+ rasterizer->SignalSyncPoint(regs.fence_action.syncpoint_id);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value());
+ }
+}
+
+void Puller::ProcessSemaphoreTriggerMethod() {
+ const auto semaphoreOperationMask = 0xF;
+ const auto op =
+ static_cast<GpuSemaphoreOperation>(regs.semaphore_trigger & semaphoreOperationMask);
+ if (op == GpuSemaphoreOperation::WriteLong) {
+ const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
+ const u32 payload = regs.semaphore_sequence;
+ std::function<void()> operation([this, sequence_address, payload] {
+ memory_manager.Write<u64>(sequence_address + sizeof(u64), gpu.GetTicks());
+ memory_manager.Write<u64>(sequence_address, payload);
+ });
+ rasterizer->SignalFence(std::move(operation));
+ } else {
+ do {
+ const u32 word{memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress())};
+ regs.acquire_source = true;
+ regs.acquire_value = regs.semaphore_sequence;
+ if (op == GpuSemaphoreOperation::AcquireEqual) {
+ regs.acquire_active = true;
+ regs.acquire_mode = false;
+ if (word != regs.acquire_value) {
+ rasterizer->ReleaseFences();
+ continue;
+ }
+ } else if (op == GpuSemaphoreOperation::AcquireGequal) {
+ regs.acquire_active = true;
+ regs.acquire_mode = true;
+ if (word < regs.acquire_value) {
+ rasterizer->ReleaseFences();
+ continue;
+ }
+ } else if (op == GpuSemaphoreOperation::AcquireMask) {
+ if (word && regs.semaphore_sequence == 0) {
+ rasterizer->ReleaseFences();
+ continue;
+ }
+ } else {
+ LOG_ERROR(HW_GPU, "Invalid semaphore operation");
+ }
+ } while (false);
+ }
+}
+
+void Puller::ProcessSemaphoreRelease() {
+ const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
+ const u32 payload = regs.semaphore_release;
+ std::function<void()> operation([this, sequence_address, payload] {
+ memory_manager.Write<u32>(sequence_address, payload);
+ });
+ rasterizer->SyncOperation(std::move(operation));
+}
+
+void Puller::ProcessSemaphoreAcquire() {
+ u32 word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
+ const auto value = regs.semaphore_acquire;
+ while (word != value) {
+ regs.acquire_active = true;
+ regs.acquire_value = value;
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ rasterizer->ReleaseFences();
+ word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
+ // TODO(kemathe73) figure out how to do the acquire_timeout
+ regs.acquire_mode = false;
+ regs.acquire_source = false;
+ }
+}
+
+/// Calls a GPU puller method.
+void Puller::CallPullerMethod(const MethodCall& method_call) {
+ regs.reg_array[method_call.method] = method_call.argument;
+ const auto method = static_cast<BufferMethods>(method_call.method);
+
+ switch (method) {
+ case BufferMethods::BindObject: {
+ ProcessBindMethod(method_call);
+ break;
+ }
+ case BufferMethods::Nop:
+ case BufferMethods::SemaphoreAddressHigh:
+ case BufferMethods::SemaphoreAddressLow:
+ case BufferMethods::SemaphoreSequencePayload:
+ case BufferMethods::SyncpointPayload:
+ break;
+ case BufferMethods::WrcacheFlush:
+ case BufferMethods::RefCnt:
+ rasterizer->SignalReference();
+ break;
+ case BufferMethods::SyncpointOperation:
+ ProcessFenceActionMethod();
+ break;
+ case BufferMethods::WaitForIdle:
+ rasterizer->WaitForIdle();
+ break;
+ case BufferMethods::SemaphoreOperation: {
+ ProcessSemaphoreTriggerMethod();
+ break;
+ }
+ case BufferMethods::NonStallInterrupt: {
+ LOG_ERROR(HW_GPU, "Special puller engine method NonStallInterrupt not implemented");
+ break;
+ }
+ case BufferMethods::MemOpA: {
+ LOG_ERROR(HW_GPU, "Memory Operation A");
+ break;
+ }
+ case BufferMethods::MemOpB: {
+ // Implement this better.
+ rasterizer->InvalidateGPUCache();
+ break;
+ }
+ case BufferMethods::MemOpC:
+ case BufferMethods::MemOpD: {
+ LOG_ERROR(HW_GPU, "Memory Operation C,D");
+ break;
+ }
+ case BufferMethods::SemaphoreAcquire: {
+ ProcessSemaphoreAcquire();
+ break;
+ }
+ case BufferMethods::SemaphoreRelease: {
+ ProcessSemaphoreRelease();
+ break;
+ }
+ case BufferMethods::Yield: {
+ // TODO(Kmather73): Research and implement this method.
+ LOG_ERROR(HW_GPU, "Special puller engine method Yield not implemented");
+ break;
+ }
+ default:
+ LOG_ERROR(HW_GPU, "Special puller engine method {:X} not implemented", method);
+ break;
+ }
+}
+
+/// Calls a GPU engine method.
+void Puller::CallEngineMethod(const MethodCall& method_call) {
+ const EngineID engine = bound_engines[method_call.subchannel];
+
+ switch (engine) {
+ case EngineID::FERMI_TWOD_A:
+ channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument,
+ method_call.IsLastCall());
+ break;
+ case EngineID::MAXWELL_B:
+ channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument,
+ method_call.IsLastCall());
+ break;
+ case EngineID::KEPLER_COMPUTE_B:
+ channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument,
+ method_call.IsLastCall());
+ break;
+ case EngineID::MAXWELL_DMA_COPY_A:
+ channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument,
+ method_call.IsLastCall());
+ break;
+ case EngineID::KEPLER_INLINE_TO_MEMORY_B:
+ channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument,
+ method_call.IsLastCall());
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented engine");
+ }
+}
+
+/// Calls a GPU engine multivalue method.
+void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
+ u32 methods_pending) {
+ const EngineID engine = bound_engines[subchannel];
+
+ switch (engine) {
+ case EngineID::FERMI_TWOD_A:
+ channel_state.fermi_2d->CallMultiMethod(method, base_start, amount, methods_pending);
+ break;
+ case EngineID::MAXWELL_B:
+ channel_state.maxwell_3d->CallMultiMethod(method, base_start, amount, methods_pending);
+ break;
+ case EngineID::KEPLER_COMPUTE_B:
+ channel_state.kepler_compute->CallMultiMethod(method, base_start, amount, methods_pending);
+ break;
+ case EngineID::MAXWELL_DMA_COPY_A:
+ channel_state.maxwell_dma->CallMultiMethod(method, base_start, amount, methods_pending);
+ break;
+ case EngineID::KEPLER_INLINE_TO_MEMORY_B:
+ channel_state.kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented engine");
+ }
+}
+
+/// Calls a GPU method.
+void Puller::CallMethod(const MethodCall& method_call) {
+ LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method_call.method,
+ method_call.subchannel);
+
+ ASSERT(method_call.subchannel < bound_engines.size());
+
+ if (ExecuteMethodOnEngine(method_call.method)) {
+ CallEngineMethod(method_call);
+ } else {
+ CallPullerMethod(method_call);
+ }
+}
+
+/// Calls a GPU multivalue method.
+void Puller::CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
+ u32 methods_pending) {
+ LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method, subchannel);
+
+ ASSERT(subchannel < bound_engines.size());
+
+ if (ExecuteMethodOnEngine(method)) {
+ CallEngineMultiMethod(method, subchannel, base_start, amount, methods_pending);
+ } else {
+ for (std::size_t i = 0; i < amount; i++) {
+ CallPullerMethod(MethodCall{
+ method,
+ base_start[i],
+ subchannel,
+ methods_pending - static_cast<u32>(i),
+ });
+ }
+ }
+}
+
+void Puller::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
+ rasterizer = rasterizer_;
+}
+
+/// Determines where the method should be executed.
+[[nodiscard]] bool Puller::ExecuteMethodOnEngine(u32 method) {
+ const auto buffer_method = static_cast<BufferMethods>(method);
+ return buffer_method >= BufferMethods::NonPullerMethods;
+}
+
+} // namespace Tegra::Engines
diff --git a/src/video_core/engines/puller.h b/src/video_core/engines/puller.h
new file mode 100644
index 000000000..d4175ee94
--- /dev/null
+++ b/src/video_core/engines/puller.h
@@ -0,0 +1,177 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <vector>
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "video_core/engines/engine_interface.h"
+
+namespace Core {
+class System;
+}
+
+namespace Tegra {
+class MemoryManager;
+class DmaPusher;
+
+enum class EngineID {
+ FERMI_TWOD_A = 0x902D, // 2D Engine
+ MAXWELL_B = 0xB197, // 3D Engine
+ KEPLER_COMPUTE_B = 0xB1C0,
+ KEPLER_INLINE_TO_MEMORY_B = 0xA140,
+ MAXWELL_DMA_COPY_A = 0xB0B5,
+};
+
+namespace Control {
+struct ChannelState;
+}
+} // namespace Tegra
+
+namespace VideoCore {
+class RasterizerInterface;
+}
+
+namespace Tegra::Engines {
+
+class Puller final {
+public:
+ struct MethodCall {
+ u32 method{};
+ u32 argument{};
+ u32 subchannel{};
+ u32 method_count{};
+
+ explicit MethodCall(u32 method_, u32 argument_, u32 subchannel_ = 0, u32 method_count_ = 0)
+ : method(method_), argument(argument_), subchannel(subchannel_),
+ method_count(method_count_) {}
+
+ [[nodiscard]] bool IsLastCall() const {
+ return method_count <= 1;
+ }
+ };
+
+ enum class FenceOperation : u32 {
+ Acquire = 0,
+ Increment = 1,
+ };
+
+ union FenceAction {
+ u32 raw;
+ BitField<0, 1, FenceOperation> op;
+ BitField<8, 24, u32> syncpoint_id;
+ };
+
+ explicit Puller(GPU& gpu_, MemoryManager& memory_manager_, DmaPusher& dma_pusher,
+ Control::ChannelState& channel_state);
+ ~Puller();
+
+ void CallMethod(const MethodCall& method_call);
+
+ void CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
+ u32 methods_pending);
+
+ void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
+
+ void CallPullerMethod(const MethodCall& method_call);
+
+ void CallEngineMethod(const MethodCall& method_call);
+
+ void CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
+ u32 methods_pending);
+
+private:
+ Tegra::GPU& gpu;
+
+ MemoryManager& memory_manager;
+ DmaPusher& dma_pusher;
+ Control::ChannelState& channel_state;
+ VideoCore::RasterizerInterface* rasterizer = nullptr;
+
+ static constexpr std::size_t NUM_REGS = 0x800;
+ struct Regs {
+ static constexpr size_t NUM_REGS = 0x40;
+
+ union {
+ struct {
+ INSERT_PADDING_WORDS_NOINIT(0x4);
+ struct {
+ u32 address_high;
+ u32 address_low;
+
+ [[nodiscard]] GPUVAddr SemaphoreAddress() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
+ }
+ } semaphore_address;
+
+ u32 semaphore_sequence;
+ u32 semaphore_trigger;
+ INSERT_PADDING_WORDS_NOINIT(0xC);
+
+ // The pusher and the puller share the reference counter, the pusher only has read
+ // access
+ u32 reference_count;
+ INSERT_PADDING_WORDS_NOINIT(0x5);
+
+ u32 semaphore_acquire;
+ u32 semaphore_release;
+ u32 fence_value;
+ FenceAction fence_action;
+ INSERT_PADDING_WORDS_NOINIT(0xE2);
+
+ // Puller state
+ u32 acquire_mode;
+ u32 acquire_source;
+ u32 acquire_active;
+ u32 acquire_timeout;
+ u32 acquire_value;
+ };
+ std::array<u32, NUM_REGS> reg_array;
+ };
+ } regs{};
+
+ void ProcessBindMethod(const MethodCall& method_call);
+ void ProcessFenceActionMethod();
+ void ProcessSemaphoreAcquire();
+ void ProcessSemaphoreRelease();
+ void ProcessSemaphoreTriggerMethod();
+ [[nodiscard]] bool ExecuteMethodOnEngine(u32 method);
+
+ /// Mapping of command subchannels to their bound engine ids
+ std::array<EngineID, 8> bound_engines{};
+
+ enum class GpuSemaphoreOperation {
+ AcquireEqual = 0x1,
+ WriteLong = 0x2,
+ AcquireGequal = 0x4,
+ AcquireMask = 0x8,
+ };
+
+#define ASSERT_REG_POSITION(field_name, position) \
+ static_assert(offsetof(Regs, field_name) == position * 4, \
+ "Field " #field_name " has invalid position")
+
+ ASSERT_REG_POSITION(semaphore_address, 0x4);
+ ASSERT_REG_POSITION(semaphore_sequence, 0x6);
+ ASSERT_REG_POSITION(semaphore_trigger, 0x7);
+ ASSERT_REG_POSITION(reference_count, 0x14);
+ ASSERT_REG_POSITION(semaphore_acquire, 0x1A);
+ ASSERT_REG_POSITION(semaphore_release, 0x1B);
+ ASSERT_REG_POSITION(fence_value, 0x1C);
+ ASSERT_REG_POSITION(fence_action, 0x1D);
+
+ ASSERT_REG_POSITION(acquire_mode, 0x100);
+ ASSERT_REG_POSITION(acquire_source, 0x101);
+ ASSERT_REG_POSITION(acquire_active, 0x102);
+ ASSERT_REG_POSITION(acquire_timeout, 0x103);
+ ASSERT_REG_POSITION(acquire_value, 0x104);
+
+#undef ASSERT_REG_POSITION
+};
+
+} // namespace Tegra::Engines
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h
index 34dc6c596..c390ac91b 100644
--- a/src/video_core/fence_manager.h
+++ b/src/video_core/fence_manager.h
@@ -1,46 +1,27 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
+#include <cstring>
+#include <deque>
+#include <functional>
+#include <memory>
#include <queue>
#include "common/common_types.h"
-#include "common/settings.h"
-#include "core/core.h"
#include "video_core/delayed_destruction_ring.h"
#include "video_core/gpu.h"
-#include "video_core/memory_manager.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/host1x/syncpoint_manager.h"
#include "video_core/rasterizer_interface.h"
namespace VideoCommon {
class FenceBase {
public:
- explicit FenceBase(u32 payload_, bool is_stubbed_)
- : address{}, payload{payload_}, is_semaphore{false}, is_stubbed{is_stubbed_} {}
-
- explicit FenceBase(GPUVAddr address_, u32 payload_, bool is_stubbed_)
- : address{address_}, payload{payload_}, is_semaphore{true}, is_stubbed{is_stubbed_} {}
-
- GPUVAddr GetAddress() const {
- return address;
- }
-
- u32 GetPayload() const {
- return payload;
- }
-
- bool IsSemaphore() const {
- return is_semaphore;
- }
-
-private:
- GPUVAddr address;
- u32 payload;
- bool is_semaphore;
+ explicit FenceBase(bool is_stubbed_) : is_stubbed{is_stubbed_} {}
protected:
bool is_stubbed;
@@ -60,30 +41,28 @@ public:
buffer_cache.AccumulateFlushes();
}
- void SignalSemaphore(GPUVAddr addr, u32 value) {
+ void SyncOperation(std::function<void()>&& func) {
+ uncommitted_operations.emplace_back(std::move(func));
+ }
+
+ void SignalFence(std::function<void()>&& func) {
TryReleasePendingFences();
const bool should_flush = ShouldFlush();
CommitAsyncFlushes();
- TFence new_fence = CreateFence(addr, value, !should_flush);
+ uncommitted_operations.emplace_back(std::move(func));
+ CommitOperations();
+ TFence new_fence = CreateFence(!should_flush);
fences.push(new_fence);
QueueFence(new_fence);
if (should_flush) {
rasterizer.FlushCommands();
}
- rasterizer.SyncGuestHost();
}
void SignalSyncPoint(u32 value) {
- TryReleasePendingFences();
- const bool should_flush = ShouldFlush();
- CommitAsyncFlushes();
- TFence new_fence = CreateFence(value, !should_flush);
- fences.push(new_fence);
- QueueFence(new_fence);
- if (should_flush) {
- rasterizer.FlushCommands();
- }
- rasterizer.SyncGuestHost();
+ syncpoint_manager.IncrementGuest(value);
+ std::function<void()> func([this, value] { syncpoint_manager.IncrementHost(value); });
+ SignalFence(std::move(func));
}
void WaitPendingFences() {
@@ -93,11 +72,10 @@ public:
WaitFence(current_fence);
}
PopAsyncFlushes();
- if (current_fence->IsSemaphore()) {
- gpu_memory.template Write<u32>(current_fence->GetAddress(),
- current_fence->GetPayload());
- } else {
- gpu.IncrementSyncPoint(current_fence->GetPayload());
+ auto operations = std::move(pending_operations.front());
+ pending_operations.pop_front();
+ for (auto& operation : operations) {
+ operation();
}
PopFence();
}
@@ -107,16 +85,14 @@ protected:
explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
TTextureCache& texture_cache_, TTBufferCache& buffer_cache_,
TQueryCache& query_cache_)
- : rasterizer{rasterizer_}, gpu{gpu_}, gpu_memory{gpu.MemoryManager()},
+ : rasterizer{rasterizer_}, gpu{gpu_}, syncpoint_manager{gpu.Host1x().GetSyncpointManager()},
texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {}
virtual ~FenceManager() = default;
- /// Creates a Sync Point Fence Interface, does not create a backend fence if 'is_stubbed' is
+ /// Creates a Fence Interface, does not create a backend fence if 'is_stubbed' is
/// true
- virtual TFence CreateFence(u32 value, bool is_stubbed) = 0;
- /// Creates a Semaphore Fence Interface, does not create a backend fence if 'is_stubbed' is true
- virtual TFence CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) = 0;
+ virtual TFence CreateFence(bool is_stubbed) = 0;
/// Queues a fence into the backend if the fence isn't stubbed.
virtual void QueueFence(TFence& fence) = 0;
/// Notifies that the backend fence has been signaled/reached in host GPU.
@@ -126,7 +102,7 @@ protected:
VideoCore::RasterizerInterface& rasterizer;
Tegra::GPU& gpu;
- Tegra::MemoryManager& gpu_memory;
+ Tegra::Host1x::SyncpointManager& syncpoint_manager;
TTextureCache& texture_cache;
TTBufferCache& buffer_cache;
TQueryCache& query_cache;
@@ -139,11 +115,10 @@ private:
return;
}
PopAsyncFlushes();
- if (current_fence->IsSemaphore()) {
- gpu_memory.template Write<u32>(current_fence->GetAddress(),
- current_fence->GetPayload());
- } else {
- gpu.IncrementSyncPoint(current_fence->GetPayload());
+ auto operations = std::move(pending_operations.front());
+ pending_operations.pop_front();
+ for (auto& operation : operations) {
+ operation();
}
PopFence();
}
@@ -162,16 +137,20 @@ private:
}
void PopAsyncFlushes() {
- std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
- texture_cache.PopAsyncFlushes();
- buffer_cache.PopAsyncFlushes();
+ {
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+ texture_cache.PopAsyncFlushes();
+ buffer_cache.PopAsyncFlushes();
+ }
query_cache.PopAsyncFlushes();
}
void CommitAsyncFlushes() {
- std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
- texture_cache.CommitAsyncFlushes();
- buffer_cache.CommitAsyncFlushes();
+ {
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+ texture_cache.CommitAsyncFlushes();
+ buffer_cache.CommitAsyncFlushes();
+ }
query_cache.CommitAsyncFlushes();
}
@@ -180,7 +159,13 @@ private:
fences.pop();
}
+ void CommitOperations() {
+ pending_operations.emplace_back(std::move(uncommitted_operations));
+ }
+
std::queue<TFence> fences;
+ std::deque<std::function<void()>> uncommitted_operations;
+ std::deque<std::deque<std::function<void()>>> pending_operations;
DelayedDestructionRing<TFence, 6> delayed_destruction_ring;
};
diff --git a/src/video_core/framebuffer_config.h b/src/video_core/framebuffer_config.h
index b1d455e30..d93f5a37f 100644
--- a/src/video_core/framebuffer_config.h
+++ b/src/video_core/framebuffer_config.h
@@ -1,46 +1,26 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#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..28b38273e 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <atomic>
@@ -15,10 +14,11 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
-#include "core/hardware_interrupt_manager.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/perf_stats.h"
#include "video_core/cdma_pusher.h"
+#include "video_core/control/channel_state.h"
+#include "video_core/control/scheduler.h"
#include "video_core/dma_pusher.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/engines/kepler_compute.h"
@@ -27,75 +27,64 @@
#include "video_core/engines/maxwell_dma.h"
#include "video_core/gpu.h"
#include "video_core/gpu_thread.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/host1x/syncpoint_manager.h"
#include "video_core/memory_manager.h"
#include "video_core/renderer_base.h"
#include "video_core/shader_notify.h"
namespace Tegra {
-MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192));
-
struct GPU::Impl {
explicit Impl(GPU& gpu_, Core::System& system_, bool is_async_, bool use_nvdec_)
- : gpu{gpu_}, system{system_}, memory_manager{std::make_unique<Tegra::MemoryManager>(
- system)},
- dma_pusher{std::make_unique<Tegra::DmaPusher>(system, gpu)}, use_nvdec{use_nvdec_},
- maxwell_3d{std::make_unique<Engines::Maxwell3D>(system, *memory_manager)},
- fermi_2d{std::make_unique<Engines::Fermi2D>()},
- kepler_compute{std::make_unique<Engines::KeplerCompute>(system, *memory_manager)},
- maxwell_dma{std::make_unique<Engines::MaxwellDMA>(system, *memory_manager)},
- kepler_memory{std::make_unique<Engines::KeplerMemory>(system, *memory_manager)},
+ : gpu{gpu_}, system{system_}, host1x{system.Host1x()}, use_nvdec{use_nvdec_},
shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_},
- gpu_thread{system_, is_async_} {}
+ gpu_thread{system_, is_async_}, scheduler{std::make_unique<Control::Scheduler>(gpu)} {}
~Impl() = default;
- /// Binds a renderer to the GPU.
- void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer_) {
- renderer = std::move(renderer_);
- rasterizer = renderer->ReadRasterizer();
-
- memory_manager->BindRasterizer(rasterizer);
- maxwell_3d->BindRasterizer(rasterizer);
- fermi_2d->BindRasterizer(rasterizer);
- kepler_compute->BindRasterizer(rasterizer);
- kepler_memory->BindRasterizer(rasterizer);
- maxwell_dma->BindRasterizer(rasterizer);
+ std::shared_ptr<Control::ChannelState> CreateChannel(s32 channel_id) {
+ auto channel_state = std::make_shared<Tegra::Control::ChannelState>(channel_id);
+ channels.emplace(channel_id, channel_state);
+ scheduler->DeclareChannel(channel_state);
+ return channel_state;
}
- /// Calls a GPU method.
- void CallMethod(const GPU::MethodCall& method_call) {
- LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method_call.method,
- method_call.subchannel);
+ void BindChannel(s32 channel_id) {
+ if (bound_channel == channel_id) {
+ return;
+ }
+ auto it = channels.find(channel_id);
+ ASSERT(it != channels.end());
+ bound_channel = channel_id;
+ current_channel = it->second.get();
- ASSERT(method_call.subchannel < bound_engines.size());
+ rasterizer->BindChannel(*current_channel);
+ }
- if (ExecuteMethodOnEngine(method_call.method)) {
- CallEngineMethod(method_call);
- } else {
- CallPullerMethod(method_call);
- }
+ std::shared_ptr<Control::ChannelState> AllocateChannel() {
+ return CreateChannel(new_channel_id++);
}
- /// Calls a GPU multivalue method.
- void CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
- u32 methods_pending) {
- LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method, subchannel);
+ void InitChannel(Control::ChannelState& to_init) {
+ to_init.Init(system, gpu);
+ to_init.BindRasterizer(rasterizer);
+ rasterizer->InitializeChannel(to_init);
+ }
- ASSERT(subchannel < bound_engines.size());
+ void InitAddressSpace(Tegra::MemoryManager& memory_manager) {
+ memory_manager.BindRasterizer(rasterizer);
+ }
- if (ExecuteMethodOnEngine(method)) {
- CallEngineMultiMethod(method, subchannel, base_start, amount, methods_pending);
- } else {
- for (std::size_t i = 0; i < amount; i++) {
- CallPullerMethod(GPU::MethodCall{
- method,
- base_start[i],
- subchannel,
- methods_pending - static_cast<u32>(i),
- });
- }
- }
+ void ReleaseChannel(Control::ChannelState& to_release) {
+ UNIMPLEMENTED();
+ }
+
+ /// Binds a renderer to the GPU.
+ void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer_) {
+ renderer = std::move(renderer_);
+ rasterizer = renderer->ReadRasterizer();
+ host1x.MemoryManager().BindRasterizer(rasterizer);
}
/// Flush all current written commands into the host GPU for execution.
@@ -104,85 +93,82 @@ struct GPU::Impl {
}
/// Synchronizes CPU writes with Host GPU memory.
- void SyncGuestHost() {
- rasterizer->SyncGuestHost();
+ void InvalidateGPUCache() {
+ rasterizer->InvalidateGPUCache();
}
/// Signal the ending of command list.
void OnCommandListEnd() {
- if (is_async) {
- // This command only applies to asynchronous GPU mode
- gpu_thread.OnCommandListEnd();
- }
+ gpu_thread.OnCommandListEnd();
}
/// Request a host GPU memory flush from the CPU.
- [[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size) {
- std::unique_lock lck{flush_request_mutex};
- const u64 fence = ++last_flush_fence;
- flush_requests.emplace_back(fence, addr, size);
+ template <typename Func>
+ [[nodiscard]] u64 RequestSyncOperation(Func&& action) {
+ std::unique_lock lck{sync_request_mutex};
+ const u64 fence = ++last_sync_fence;
+ sync_requests.emplace_back(action);
return fence;
}
/// Obtains current flush request fence id.
- [[nodiscard]] u64 CurrentFlushRequestFence() const {
- return current_flush_fence.load(std::memory_order_relaxed);
+ [[nodiscard]] u64 CurrentSyncRequestFence() const {
+ return current_sync_fence.load(std::memory_order_relaxed);
+ }
+
+ void WaitForSyncOperation(const u64 fence) {
+ std::unique_lock lck{sync_request_mutex};
+ sync_request_cv.wait(lck, [this, fence] { return CurrentSyncRequestFence() >= fence; });
}
/// Tick pending requests within the GPU.
void TickWork() {
- std::unique_lock lck{flush_request_mutex};
- while (!flush_requests.empty()) {
- auto& request = flush_requests.front();
- const u64 fence = request.fence;
- const VAddr addr = request.addr;
- const std::size_t size = request.size;
- flush_requests.pop_front();
- flush_request_mutex.unlock();
- rasterizer->FlushRegion(addr, size);
- current_flush_fence.store(fence);
- flush_request_mutex.lock();
+ std::unique_lock lck{sync_request_mutex};
+ while (!sync_requests.empty()) {
+ auto request = std::move(sync_requests.front());
+ sync_requests.pop_front();
+ sync_request_mutex.unlock();
+ request();
+ current_sync_fence.fetch_add(1, std::memory_order_release);
+ sync_request_mutex.lock();
+ sync_request_cv.notify_all();
}
}
/// Returns a reference to the Maxwell3D GPU engine.
[[nodiscard]] Engines::Maxwell3D& Maxwell3D() {
- return *maxwell_3d;
+ ASSERT(current_channel);
+ return *current_channel->maxwell_3d;
}
/// Returns a const reference to the Maxwell3D GPU engine.
[[nodiscard]] const Engines::Maxwell3D& Maxwell3D() const {
- return *maxwell_3d;
+ ASSERT(current_channel);
+ return *current_channel->maxwell_3d;
}
/// Returns a reference to the KeplerCompute GPU engine.
[[nodiscard]] Engines::KeplerCompute& KeplerCompute() {
- return *kepler_compute;
+ ASSERT(current_channel);
+ return *current_channel->kepler_compute;
}
/// Returns a reference to the KeplerCompute GPU engine.
[[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const {
- return *kepler_compute;
- }
-
- /// Returns a reference to the GPU memory manager.
- [[nodiscard]] Tegra::MemoryManager& MemoryManager() {
- return *memory_manager;
- }
-
- /// Returns a const reference to the GPU memory manager.
- [[nodiscard]] const Tegra::MemoryManager& MemoryManager() const {
- return *memory_manager;
+ ASSERT(current_channel);
+ return *current_channel->kepler_compute;
}
/// Returns a reference to the GPU DMA pusher.
[[nodiscard]] Tegra::DmaPusher& DmaPusher() {
- return *dma_pusher;
+ ASSERT(current_channel);
+ return *current_channel->dma_pusher;
}
/// Returns a const reference to the GPU DMA pusher.
[[nodiscard]] const Tegra::DmaPusher& DmaPusher() const {
- return *dma_pusher;
+ ASSERT(current_channel);
+ return *current_channel->dma_pusher;
}
/// Returns a reference to the underlying renderer.
@@ -205,77 +191,6 @@ struct GPU::Impl {
return *shader_notify;
}
- /// Allows the CPU/NvFlinger to wait on the GPU before presenting a frame.
- void WaitFence(u32 syncpoint_id, u32 value) {
- // Synced GPU, is always in sync
- if (!is_async) {
- return;
- }
- if (syncpoint_id == UINT32_MAX) {
- // TODO: Research what this does.
- LOG_ERROR(HW_GPU, "Waiting for syncpoint -1 not implemented");
- return;
- }
- MICROPROFILE_SCOPE(GPU_wait);
- std::unique_lock lock{sync_mutex};
- sync_cv.wait(lock, [=, this] {
- if (shutting_down.load(std::memory_order_relaxed)) {
- // We're shutting down, ensure no threads continue to wait for the next syncpoint
- return true;
- }
- return syncpoints.at(syncpoint_id).load() >= value;
- });
- }
-
- void IncrementSyncPoint(u32 syncpoint_id) {
- auto& syncpoint = syncpoints.at(syncpoint_id);
- syncpoint++;
- std::lock_guard lock{sync_mutex};
- sync_cv.notify_all();
- auto& interrupt = syncpt_interrupts.at(syncpoint_id);
- if (!interrupt.empty()) {
- u32 value = syncpoint.load();
- auto it = interrupt.begin();
- while (it != interrupt.end()) {
- if (value >= *it) {
- TriggerCpuInterrupt(syncpoint_id, *it);
- it = interrupt.erase(it);
- continue;
- }
- it++;
- }
- }
- }
-
- [[nodiscard]] u32 GetSyncpointValue(u32 syncpoint_id) const {
- return syncpoints.at(syncpoint_id).load();
- }
-
- void RegisterSyncptInterrupt(u32 syncpoint_id, u32 value) {
- std::lock_guard 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; });
- if (contains) {
- return;
- }
- interrupt.emplace_back(value);
- }
-
- [[nodiscard]] bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value) {
- std::lock_guard lock{sync_mutex};
- auto& interrupt = syncpt_interrupts.at(syncpoint_id);
- const auto iter =
- std::find_if(interrupt.begin(), interrupt.end(),
- [value](u32 interrupt_value) { return value == interrupt_value; });
-
- if (iter == interrupt.end()) {
- return false;
- }
- interrupt.erase(iter);
- return true;
- }
-
[[nodiscard]] u64 GetTicks() const {
// This values were reversed engineered by fincs from NVN
// The gpu clock is reported in units of 385/625 nanoseconds
@@ -307,7 +222,7 @@ struct GPU::Impl {
/// This can be used to launch any necessary threads and register any necessary
/// core timing events.
void Start() {
- gpu_thread.StartThread(*renderer, renderer->Context(), *dma_pusher);
+ gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler);
cpu_context = renderer->GetRenderWindow().CreateSharedContext();
cpu_context->MakeCurrent();
}
@@ -329,8 +244,8 @@ struct GPU::Impl {
}
/// Push GPU command entries to be processed
- void PushGPUEntries(Tegra::CommandList&& entries) {
- gpu_thread.SubmitList(std::move(entries));
+ void PushGPUEntries(s32 channel, Tegra::CommandList&& entries) {
+ gpu_thread.SubmitList(channel, std::move(entries));
}
/// Push GPU command buffer entries to be processed
@@ -340,7 +255,7 @@ struct GPU::Impl {
}
if (!cdma_pushers.contains(id)) {
- cdma_pushers.insert_or_assign(id, std::make_unique<Tegra::CDmaPusher>(gpu));
+ cdma_pushers.insert_or_assign(id, std::make_unique<Tegra::CDmaPusher>(host1x));
}
// SubmitCommandBuffer would make the nvdec operations async, this is not currently working
@@ -377,308 +292,55 @@ struct GPU::Impl {
gpu_thread.FlushAndInvalidateRegion(addr, size);
}
- void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const {
- auto& interrupt_manager = system.InterruptManager();
- interrupt_manager.GPUInterruptSyncpt(syncpoint_id, value);
- }
-
- void ProcessBindMethod(const GPU::MethodCall& method_call) {
- // Bind the current subchannel to the desired engine id.
- LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", method_call.subchannel,
- method_call.argument);
- const auto engine_id = static_cast<EngineID>(method_call.argument);
- bound_engines[method_call.subchannel] = static_cast<EngineID>(engine_id);
- switch (engine_id) {
- case EngineID::FERMI_TWOD_A:
- dma_pusher->BindSubchannel(fermi_2d.get(), method_call.subchannel);
- break;
- case EngineID::MAXWELL_B:
- dma_pusher->BindSubchannel(maxwell_3d.get(), method_call.subchannel);
- break;
- case EngineID::KEPLER_COMPUTE_B:
- dma_pusher->BindSubchannel(kepler_compute.get(), method_call.subchannel);
- break;
- case EngineID::MAXWELL_DMA_COPY_A:
- dma_pusher->BindSubchannel(maxwell_dma.get(), method_call.subchannel);
- break;
- case EngineID::KEPLER_INLINE_TO_MEMORY_B:
- dma_pusher->BindSubchannel(kepler_memory.get(), method_call.subchannel);
- break;
- default:
- UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
- }
- }
-
- void ProcessFenceActionMethod() {
- switch (regs.fence_action.op) {
- case GPU::FenceOperation::Acquire:
- WaitFence(regs.fence_action.syncpoint_id, regs.fence_value);
- break;
- case GPU::FenceOperation::Increment:
- IncrementSyncPoint(regs.fence_action.syncpoint_id);
- break;
- default:
- UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value());
- }
- }
-
- void ProcessWaitForInterruptMethod() {
- // TODO(bunnei) ImplementMe
- LOG_WARNING(HW_GPU, "(STUBBED) called");
- }
-
- void ProcessSemaphoreTriggerMethod() {
- const auto semaphoreOperationMask = 0xF;
- const auto op =
- static_cast<GpuSemaphoreOperation>(regs.semaphore_trigger & semaphoreOperationMask);
- if (op == GpuSemaphoreOperation::WriteLong) {
- struct Block {
- u32 sequence;
- u32 zeros = 0;
- u64 timestamp;
- };
-
- Block block{};
- block.sequence = regs.semaphore_sequence;
- // TODO(Kmather73): Generate a real GPU timestamp and write it here instead of
- // CoreTiming
- block.timestamp = GetTicks();
- memory_manager->WriteBlock(regs.semaphore_address.SemaphoreAddress(), &block,
- sizeof(block));
- } else {
- const u32 word{memory_manager->Read<u32>(regs.semaphore_address.SemaphoreAddress())};
- if ((op == GpuSemaphoreOperation::AcquireEqual && word == regs.semaphore_sequence) ||
- (op == GpuSemaphoreOperation::AcquireGequal &&
- static_cast<s32>(word - regs.semaphore_sequence) > 0) ||
- (op == GpuSemaphoreOperation::AcquireMask && (word & regs.semaphore_sequence))) {
- // Nothing to do in this case
+ void RequestSwapBuffers(const Tegra::FramebufferConfig* framebuffer,
+ std::array<Service::Nvidia::NvFence, 4>& fences, size_t num_fences) {
+ size_t current_request_counter{};
+ {
+ std::unique_lock<std::mutex> lk(request_swap_mutex);
+ if (free_swap_counters.empty()) {
+ current_request_counter = request_swap_counters.size();
+ request_swap_counters.emplace_back(num_fences);
} else {
- regs.acquire_source = true;
- regs.acquire_value = regs.semaphore_sequence;
- if (op == GpuSemaphoreOperation::AcquireEqual) {
- regs.acquire_active = true;
- regs.acquire_mode = false;
- } else if (op == GpuSemaphoreOperation::AcquireGequal) {
- regs.acquire_active = true;
- regs.acquire_mode = true;
- } else if (op == GpuSemaphoreOperation::AcquireMask) {
- // TODO(kemathe) The acquire mask operation waits for a value that, ANDed with
- // semaphore_sequence, gives a non-0 result
- LOG_ERROR(HW_GPU, "Invalid semaphore operation AcquireMask not implemented");
- } else {
- LOG_ERROR(HW_GPU, "Invalid semaphore operation");
- }
+ current_request_counter = free_swap_counters.front();
+ request_swap_counters[current_request_counter] = num_fences;
+ free_swap_counters.pop_front();
}
}
- }
-
- void ProcessSemaphoreRelease() {
- memory_manager->Write<u32>(regs.semaphore_address.SemaphoreAddress(),
- regs.semaphore_release);
- }
-
- void ProcessSemaphoreAcquire() {
- const u32 word = memory_manager->Read<u32>(regs.semaphore_address.SemaphoreAddress());
- const auto value = regs.semaphore_acquire;
- if (word != value) {
- regs.acquire_active = true;
- regs.acquire_value = value;
- // TODO(kemathe73) figure out how to do the acquire_timeout
- regs.acquire_mode = false;
- regs.acquire_source = false;
- }
- }
-
- /// Calls a GPU puller method.
- void CallPullerMethod(const GPU::MethodCall& method_call) {
- regs.reg_array[method_call.method] = method_call.argument;
- const auto method = static_cast<BufferMethods>(method_call.method);
-
- switch (method) {
- case BufferMethods::BindObject: {
- ProcessBindMethod(method_call);
- break;
- }
- case BufferMethods::Nop:
- case BufferMethods::SemaphoreAddressHigh:
- case BufferMethods::SemaphoreAddressLow:
- case BufferMethods::SemaphoreSequence:
- break;
- case BufferMethods::UnkCacheFlush:
- rasterizer->SyncGuestHost();
- break;
- case BufferMethods::WrcacheFlush:
- rasterizer->SignalReference();
- break;
- case BufferMethods::FenceValue:
- break;
- case BufferMethods::RefCnt:
- rasterizer->SignalReference();
- break;
- case BufferMethods::FenceAction:
- ProcessFenceActionMethod();
- break;
- case BufferMethods::WaitForInterrupt:
- rasterizer->WaitForIdle();
- break;
- case BufferMethods::SemaphoreTrigger: {
- ProcessSemaphoreTriggerMethod();
- break;
- }
- case BufferMethods::NotifyIntr: {
- // TODO(Kmather73): Research and implement this method.
- LOG_ERROR(HW_GPU, "Special puller engine method NotifyIntr not implemented");
- break;
- }
- case BufferMethods::Unk28: {
- // TODO(Kmather73): Research and implement this method.
- LOG_ERROR(HW_GPU, "Special puller engine method Unk28 not implemented");
- break;
- }
- case BufferMethods::SemaphoreAcquire: {
- ProcessSemaphoreAcquire();
- break;
- }
- case BufferMethods::SemaphoreRelease: {
- ProcessSemaphoreRelease();
- break;
- }
- case BufferMethods::Yield: {
- // TODO(Kmather73): Research and implement this method.
- LOG_ERROR(HW_GPU, "Special puller engine method Yield not implemented");
- break;
- }
- default:
- LOG_ERROR(HW_GPU, "Special puller engine method {:X} not implemented", method);
- break;
- }
- }
-
- /// Calls a GPU engine method.
- void CallEngineMethod(const GPU::MethodCall& method_call) {
- const EngineID engine = bound_engines[method_call.subchannel];
-
- switch (engine) {
- case EngineID::FERMI_TWOD_A:
- fermi_2d->CallMethod(method_call.method, method_call.argument,
- method_call.IsLastCall());
- break;
- case EngineID::MAXWELL_B:
- maxwell_3d->CallMethod(method_call.method, method_call.argument,
- method_call.IsLastCall());
- break;
- case EngineID::KEPLER_COMPUTE_B:
- kepler_compute->CallMethod(method_call.method, method_call.argument,
- method_call.IsLastCall());
- break;
- case EngineID::MAXWELL_DMA_COPY_A:
- maxwell_dma->CallMethod(method_call.method, method_call.argument,
- method_call.IsLastCall());
- break;
- case EngineID::KEPLER_INLINE_TO_MEMORY_B:
- kepler_memory->CallMethod(method_call.method, method_call.argument,
- method_call.IsLastCall());
- break;
- default:
- UNIMPLEMENTED_MSG("Unimplemented engine");
- }
- }
-
- /// Calls a GPU engine multivalue method.
- void CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
- u32 methods_pending) {
- const EngineID engine = bound_engines[subchannel];
-
- switch (engine) {
- case EngineID::FERMI_TWOD_A:
- fermi_2d->CallMultiMethod(method, base_start, amount, methods_pending);
- break;
- case EngineID::MAXWELL_B:
- maxwell_3d->CallMultiMethod(method, base_start, amount, methods_pending);
- break;
- case EngineID::KEPLER_COMPUTE_B:
- kepler_compute->CallMultiMethod(method, base_start, amount, methods_pending);
- break;
- case EngineID::MAXWELL_DMA_COPY_A:
- maxwell_dma->CallMultiMethod(method, base_start, amount, methods_pending);
- break;
- case EngineID::KEPLER_INLINE_TO_MEMORY_B:
- kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending);
- break;
- default:
- UNIMPLEMENTED_MSG("Unimplemented engine");
- }
- }
-
- /// Determines where the method should be executed.
- [[nodiscard]] bool ExecuteMethodOnEngine(u32 method) {
- const auto buffer_method = static_cast<BufferMethods>(method);
- return buffer_method >= BufferMethods::NonPullerMethods;
- }
-
- struct Regs {
- static constexpr size_t NUM_REGS = 0x40;
-
- union {
- struct {
- INSERT_PADDING_WORDS_NOINIT(0x4);
- struct {
- u32 address_high;
- u32 address_low;
-
- [[nodiscard]] GPUVAddr SemaphoreAddress() const {
- return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
- address_low);
+ const auto wait_fence =
+ RequestSyncOperation([this, current_request_counter, framebuffer, fences, num_fences] {
+ auto& syncpoint_manager = host1x.GetSyncpointManager();
+ if (num_fences == 0) {
+ renderer->SwapBuffers(framebuffer);
+ }
+ const auto executer = [this, current_request_counter,
+ framebuffer_copy = *framebuffer]() {
+ {
+ std::unique_lock<std::mutex> lk(request_swap_mutex);
+ if (--request_swap_counters[current_request_counter] != 0) {
+ return;
+ }
+ free_swap_counters.push_back(current_request_counter);
}
- } semaphore_address;
-
- u32 semaphore_sequence;
- u32 semaphore_trigger;
- INSERT_PADDING_WORDS_NOINIT(0xC);
-
- // The pusher and the puller share the reference counter, the pusher only has read
- // access
- u32 reference_count;
- INSERT_PADDING_WORDS_NOINIT(0x5);
-
- u32 semaphore_acquire;
- u32 semaphore_release;
- u32 fence_value;
- GPU::FenceAction fence_action;
- INSERT_PADDING_WORDS_NOINIT(0xE2);
-
- // Puller state
- u32 acquire_mode;
- u32 acquire_source;
- u32 acquire_active;
- u32 acquire_timeout;
- u32 acquire_value;
- };
- std::array<u32, NUM_REGS> reg_array;
- };
- } regs{};
+ renderer->SwapBuffers(&framebuffer_copy);
+ };
+ for (size_t i = 0; i < num_fences; i++) {
+ syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer);
+ }
+ });
+ gpu_thread.TickGPU();
+ WaitForSyncOperation(wait_fence);
+ }
GPU& gpu;
Core::System& system;
- std::unique_ptr<Tegra::MemoryManager> memory_manager;
- std::unique_ptr<Tegra::DmaPusher> dma_pusher;
+ Host1x::Host1x& host1x;
+
std::map<u32, std::unique_ptr<Tegra::CDmaPusher>> cdma_pushers;
std::unique_ptr<VideoCore::RendererBase> renderer;
VideoCore::RasterizerInterface* rasterizer = nullptr;
const bool use_nvdec;
- /// Mapping of command subchannels to their bound engine ids
- std::array<EngineID, 8> bound_engines{};
- /// 3D engine
- std::unique_ptr<Engines::Maxwell3D> maxwell_3d;
- /// 2D engine
- std::unique_ptr<Engines::Fermi2D> fermi_2d;
- /// Compute engine
- std::unique_ptr<Engines::KeplerCompute> kepler_compute;
- /// DMA engine
- std::unique_ptr<Engines::MaxwellDMA> maxwell_dma;
- /// Inline memory engine
- std::unique_ptr<Engines::KeplerMemory> kepler_memory;
+ s32 new_channel_id{1};
/// Shader build notifier
std::unique_ptr<VideoCore::ShaderNotify> shader_notify;
/// When true, we are about to shut down emulation session, so terminate outstanding tasks
@@ -693,51 +355,25 @@ struct GPU::Impl {
std::condition_variable sync_cv;
- struct FlushRequest {
- explicit FlushRequest(u64 fence_, VAddr addr_, std::size_t size_)
- : fence{fence_}, addr{addr_}, size{size_} {}
- u64 fence;
- VAddr addr;
- std::size_t size;
- };
-
- std::list<FlushRequest> flush_requests;
- std::atomic<u64> current_flush_fence{};
- u64 last_flush_fence{};
- std::mutex flush_request_mutex;
+ std::list<std::function<void()>> sync_requests;
+ std::atomic<u64> current_sync_fence{};
+ u64 last_sync_fence{};
+ std::mutex sync_request_mutex;
+ std::condition_variable sync_request_cv;
const bool is_async;
VideoCommon::GPUThread::ThreadManager gpu_thread;
std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context;
-#define ASSERT_REG_POSITION(field_name, position) \
- static_assert(offsetof(Regs, field_name) == position * 4, \
- "Field " #field_name " has invalid position")
-
- ASSERT_REG_POSITION(semaphore_address, 0x4);
- ASSERT_REG_POSITION(semaphore_sequence, 0x6);
- ASSERT_REG_POSITION(semaphore_trigger, 0x7);
- ASSERT_REG_POSITION(reference_count, 0x14);
- ASSERT_REG_POSITION(semaphore_acquire, 0x1A);
- ASSERT_REG_POSITION(semaphore_release, 0x1B);
- ASSERT_REG_POSITION(fence_value, 0x1C);
- ASSERT_REG_POSITION(fence_action, 0x1D);
-
- ASSERT_REG_POSITION(acquire_mode, 0x100);
- ASSERT_REG_POSITION(acquire_source, 0x101);
- ASSERT_REG_POSITION(acquire_active, 0x102);
- ASSERT_REG_POSITION(acquire_timeout, 0x103);
- ASSERT_REG_POSITION(acquire_value, 0x104);
-
-#undef ASSERT_REG_POSITION
-
- enum class GpuSemaphoreOperation {
- AcquireEqual = 0x1,
- WriteLong = 0x2,
- AcquireGequal = 0x4,
- AcquireMask = 0x8,
- };
+ std::unique_ptr<Tegra::Control::Scheduler> scheduler;
+ std::unordered_map<s32, std::shared_ptr<Tegra::Control::ChannelState>> channels;
+ Tegra::Control::ChannelState* current_channel;
+ s32 bound_channel{-1};
+
+ std::deque<size_t> free_swap_counters;
+ std::deque<size_t> request_swap_counters;
+ std::mutex request_swap_mutex;
};
GPU::GPU(Core::System& system, bool is_async, bool use_nvdec)
@@ -745,25 +381,36 @@ GPU::GPU(Core::System& system, bool is_async, bool use_nvdec)
GPU::~GPU() = default;
-void GPU::BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer) {
- impl->BindRenderer(std::move(renderer));
+std::shared_ptr<Control::ChannelState> GPU::AllocateChannel() {
+ return impl->AllocateChannel();
+}
+
+void GPU::InitChannel(Control::ChannelState& to_init) {
+ impl->InitChannel(to_init);
+}
+
+void GPU::BindChannel(s32 channel_id) {
+ impl->BindChannel(channel_id);
}
-void GPU::CallMethod(const MethodCall& method_call) {
- impl->CallMethod(method_call);
+void GPU::ReleaseChannel(Control::ChannelState& to_release) {
+ impl->ReleaseChannel(to_release);
}
-void GPU::CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
- u32 methods_pending) {
- impl->CallMultiMethod(method, subchannel, base_start, amount, methods_pending);
+void GPU::InitAddressSpace(Tegra::MemoryManager& memory_manager) {
+ impl->InitAddressSpace(memory_manager);
+}
+
+void GPU::BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer) {
+ impl->BindRenderer(std::move(renderer));
}
void GPU::FlushCommands() {
impl->FlushCommands();
}
-void GPU::SyncGuestHost() {
- impl->SyncGuestHost();
+void GPU::InvalidateGPUCache() {
+ impl->InvalidateGPUCache();
}
void GPU::OnCommandListEnd() {
@@ -771,17 +418,32 @@ void GPU::OnCommandListEnd() {
}
u64 GPU::RequestFlush(VAddr addr, std::size_t size) {
- return impl->RequestFlush(addr, size);
+ return impl->RequestSyncOperation(
+ [this, addr, size]() { impl->rasterizer->FlushRegion(addr, size); });
}
-u64 GPU::CurrentFlushRequestFence() const {
- return impl->CurrentFlushRequestFence();
+u64 GPU::CurrentSyncRequestFence() const {
+ return impl->CurrentSyncRequestFence();
+}
+
+void GPU::WaitForSyncOperation(u64 fence) {
+ return impl->WaitForSyncOperation(fence);
}
void GPU::TickWork() {
impl->TickWork();
}
+/// Gets a mutable reference to the Host1x interface
+Host1x::Host1x& GPU::Host1x() {
+ return impl->host1x;
+}
+
+/// Gets an immutable reference to the Host1x interface.
+const Host1x::Host1x& GPU::Host1x() const {
+ return impl->host1x;
+}
+
Engines::Maxwell3D& GPU::Maxwell3D() {
return impl->Maxwell3D();
}
@@ -798,14 +460,6 @@ const Engines::KeplerCompute& GPU::KeplerCompute() const {
return impl->KeplerCompute();
}
-Tegra::MemoryManager& GPU::MemoryManager() {
- return impl->MemoryManager();
-}
-
-const Tegra::MemoryManager& GPU::MemoryManager() const {
- return impl->MemoryManager();
-}
-
Tegra::DmaPusher& GPU::DmaPusher() {
return impl->DmaPusher();
}
@@ -830,24 +484,9 @@ const VideoCore::ShaderNotify& GPU::ShaderNotify() const {
return impl->ShaderNotify();
}
-void GPU::WaitFence(u32 syncpoint_id, u32 value) {
- impl->WaitFence(syncpoint_id, value);
-}
-
-void GPU::IncrementSyncPoint(u32 syncpoint_id) {
- impl->IncrementSyncPoint(syncpoint_id);
-}
-
-u32 GPU::GetSyncpointValue(u32 syncpoint_id) const {
- return impl->GetSyncpointValue(syncpoint_id);
-}
-
-void GPU::RegisterSyncptInterrupt(u32 syncpoint_id, u32 value) {
- impl->RegisterSyncptInterrupt(syncpoint_id, value);
-}
-
-bool GPU::CancelSyncptInterrupt(u32 syncpoint_id, u32 value) {
- return impl->CancelSyncptInterrupt(syncpoint_id, value);
+void GPU::RequestSwapBuffers(const Tegra::FramebufferConfig* framebuffer,
+ std::array<Service::Nvidia::NvFence, 4>& fences, size_t num_fences) {
+ impl->RequestSwapBuffers(framebuffer, fences, num_fences);
}
u64 GPU::GetTicks() const {
@@ -882,8 +521,8 @@ void GPU::ReleaseContext() {
impl->ReleaseContext();
}
-void GPU::PushGPUEntries(Tegra::CommandList&& entries) {
- impl->PushGPUEntries(std::move(entries));
+void GPU::PushGPUEntries(s32 channel, Tegra::CommandList&& entries) {
+ impl->PushGPUEntries(channel, std::move(entries));
}
void GPU::PushCommandBuffer(u32 id, Tegra::ChCommandHeaderList& entries) {
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index 26b8ea233..0a4a8b14f 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,6 +7,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"
@@ -89,73 +89,58 @@ class Maxwell3D;
class KeplerCompute;
} // namespace Engines
-enum class EngineID {
- FERMI_TWOD_A = 0x902D, // 2D Engine
- MAXWELL_B = 0xB197, // 3D Engine
- KEPLER_COMPUTE_B = 0xB1C0,
- KEPLER_INLINE_TO_MEMORY_B = 0xA140,
- MAXWELL_DMA_COPY_A = 0xB0B5,
-};
+namespace Control {
+struct ChannelState;
+}
+
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
class MemoryManager;
class GPU final {
public:
- struct MethodCall {
- u32 method{};
- u32 argument{};
- u32 subchannel{};
- u32 method_count{};
-
- explicit MethodCall(u32 method_, u32 argument_, u32 subchannel_ = 0, u32 method_count_ = 0)
- : method(method_), argument(argument_), subchannel(subchannel_),
- method_count(method_count_) {}
-
- [[nodiscard]] bool IsLastCall() const {
- return method_count <= 1;
- }
- };
-
- enum class FenceOperation : u32 {
- Acquire = 0,
- Increment = 1,
- };
-
- union FenceAction {
- u32 raw;
- BitField<0, 1, FenceOperation> op;
- BitField<8, 24, u32> syncpoint_id;
- };
-
explicit GPU(Core::System& system, bool is_async, bool use_nvdec);
~GPU();
/// Binds a renderer to the GPU.
void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer);
- /// Calls a GPU method.
- void CallMethod(const MethodCall& method_call);
-
- /// Calls a GPU multivalue method.
- void CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
- u32 methods_pending);
-
/// Flush all current written commands into the host GPU for execution.
void FlushCommands();
/// Synchronizes CPU writes with Host GPU memory.
- void SyncGuestHost();
+ void InvalidateGPUCache();
/// Signal the ending of command list.
void OnCommandListEnd();
+ std::shared_ptr<Control::ChannelState> AllocateChannel();
+
+ void InitChannel(Control::ChannelState& to_init);
+
+ void BindChannel(s32 channel_id);
+
+ void ReleaseChannel(Control::ChannelState& to_release);
+
+ void InitAddressSpace(Tegra::MemoryManager& memory_manager);
+
/// Request a host GPU memory flush from the CPU.
[[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size);
/// Obtains current flush request fence id.
- [[nodiscard]] u64 CurrentFlushRequestFence() const;
+ [[nodiscard]] u64 CurrentSyncRequestFence() const;
+
+ void WaitForSyncOperation(u64 fence);
/// Tick pending requests within the GPU.
void TickWork();
+ /// Gets a mutable reference to the Host1x interface
+ [[nodiscard]] Host1x::Host1x& Host1x();
+
+ /// Gets an immutable reference to the Host1x interface.
+ [[nodiscard]] const Host1x::Host1x& Host1x() const;
+
/// Returns a reference to the Maxwell3D GPU engine.
[[nodiscard]] Engines::Maxwell3D& Maxwell3D();
@@ -168,12 +153,6 @@ public:
/// Returns a reference to the KeplerCompute GPU engine.
[[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const;
- /// Returns a reference to the GPU memory manager.
- [[nodiscard]] Tegra::MemoryManager& MemoryManager();
-
- /// Returns a const reference to the GPU memory manager.
- [[nodiscard]] const Tegra::MemoryManager& MemoryManager() const;
-
/// Returns a reference to the GPU DMA pusher.
[[nodiscard]] Tegra::DmaPusher& DmaPusher();
@@ -192,17 +171,6 @@ public:
/// Returns a const reference to the shader notifier.
[[nodiscard]] const VideoCore::ShaderNotify& ShaderNotify() const;
- /// Allows the CPU/NvFlinger to wait on the GPU before presenting a frame.
- void WaitFence(u32 syncpoint_id, u32 value);
-
- void IncrementSyncPoint(u32 syncpoint_id);
-
- [[nodiscard]] u32 GetSyncpointValue(u32 syncpoint_id) const;
-
- void RegisterSyncptInterrupt(u32 syncpoint_id, u32 value);
-
- [[nodiscard]] bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value);
-
[[nodiscard]] u64 GetTicks() const;
[[nodiscard]] bool IsAsync() const;
@@ -211,6 +179,9 @@ public:
void RendererFrameEndNotify();
+ void RequestSwapBuffers(const Tegra::FramebufferConfig* framebuffer,
+ std::array<Service::Nvidia::NvFence, 4>& fences, size_t num_fences);
+
/// Performs any additional setup necessary in order to begin GPU emulation.
/// This can be used to launch any necessary threads and register any necessary
/// core timing events.
@@ -226,7 +197,7 @@ public:
void ReleaseContext();
/// Push GPU command entries to be processed
- void PushGPUEntries(Tegra::CommandList&& entries);
+ void PushGPUEntries(s32 channel, Tegra::CommandList&& entries);
/// Push GPU command buffer entries to be processed
void PushCommandBuffer(u32 id, Tegra::ChCommandHeaderList& entries);
@@ -248,7 +219,7 @@ public:
private:
struct Impl;
- std::unique_ptr<Impl> impl;
+ mutable std::unique_ptr<Impl> impl;
};
} // namespace Tegra
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index 9547f277a..1bd477011 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/microprofile.h"
@@ -9,6 +8,7 @@
#include "common/thread.h"
#include "core/core.h"
#include "core/frontend/emu_window.h"
+#include "video_core/control/scheduler.h"
#include "video_core/dma_pusher.h"
#include "video_core/gpu.h"
#include "video_core/gpu_thread.h"
@@ -19,8 +19,8 @@ namespace VideoCommon::GPUThread {
/// Runs the GPU thread
static void RunThread(std::stop_token stop_token, Core::System& system,
VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
- Tegra::DmaPusher& dma_pusher, SynchState& state) {
- std::string name = "yuzu:GPU";
+ Tegra::Control::Scheduler& scheduler, SynchState& state) {
+ std::string name = "GPU";
MicroProfileOnThreadCreate(name.c_str());
SCOPE_EXIT({ MicroProfileOnThreadExit(); });
@@ -37,8 +37,7 @@ static void RunThread(std::stop_token stop_token, Core::System& system,
break;
}
if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
- dma_pusher.Push(std::move(submit_list->entries));
- dma_pusher.DispatchCalls();
+ scheduler.Push(submit_list->channel, std::move(submit_list->entries));
} else if (const auto* data = std::get_if<SwapBuffersCommand>(&next.data)) {
renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr);
} else if (std::holds_alternative<OnCommandListEndCommand>(next.data)) {
@@ -50,13 +49,13 @@ static void RunThread(std::stop_token stop_token, Core::System& system,
} else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) {
rasterizer->OnCPUWrite(invalidate->addr, invalidate->size);
} else {
- UNREACHABLE();
+ ASSERT(false);
}
state.signaled_fence.store(next.fence);
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();
}
}
@@ -69,14 +68,14 @@ ThreadManager::~ThreadManager() = default;
void ThreadManager::StartThread(VideoCore::RendererBase& renderer,
Core::Frontend::GraphicsContext& context,
- Tegra::DmaPusher& dma_pusher) {
+ Tegra::Control::Scheduler& scheduler) {
rasterizer = renderer.ReadRasterizer();
thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
- std::ref(dma_pusher), std::ref(state));
+ std::ref(scheduler), std::ref(state));
}
-void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
- PushCommand(SubmitListCommand(std::move(entries)));
+void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries) {
+ PushCommand(SubmitListCommand(channel, std::move(entries)));
}
void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
@@ -94,8 +93,12 @@ void ThreadManager::FlushRegion(VAddr addr, u64 size) {
}
auto& gpu = system.GPU();
u64 fence = gpu.RequestFlush(addr, size);
- PushCommand(GPUTickCommand(), true);
- ASSERT(fence <= gpu.CurrentFlushRequestFence());
+ TickGPU();
+ gpu.WaitForSyncOperation(fence);
+}
+
+void ThreadManager::TickGPU() {
+ PushCommand(GPUTickCommand());
}
void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {
diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h
index 00984188e..64628d3e3 100644
--- a/src/video_core/gpu_thread.h
+++ b/src/video_core/gpu_thread.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -16,7 +15,9 @@
namespace Tegra {
struct FramebufferConfig;
-class DmaPusher;
+namespace Control {
+class Scheduler;
+}
} // namespace Tegra
namespace Core {
@@ -35,8 +36,10 @@ namespace VideoCommon::GPUThread {
/// Command to signal to the GPU thread that a command list is ready for processing
struct SubmitListCommand final {
- explicit SubmitListCommand(Tegra::CommandList&& entries_) : entries{std::move(entries_)} {}
+ explicit SubmitListCommand(s32 channel_, Tegra::CommandList&& entries_)
+ : channel{channel_}, entries{std::move(entries_)} {}
+ s32 channel;
Tegra::CommandList entries;
};
@@ -97,7 +100,7 @@ struct CommandDataContainer {
/// Struct used to synchronize the GPU thread
struct SynchState final {
- using CommandQueue = Common::SPSCQueue<CommandDataContainer, true>;
+ using CommandQueue = Common::MPSCQueue<CommandDataContainer, true>;
std::mutex write_lock;
CommandQueue queue;
u64 last_fence{};
@@ -113,10 +116,10 @@ public:
/// Creates and starts the GPU thread.
void StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
- Tegra::DmaPusher& dma_pusher);
+ Tegra::Control::Scheduler& scheduler);
/// Push GPU command entries to be processed
- void SubmitList(Tegra::CommandList&& entries);
+ void SubmitList(s32 channel, Tegra::CommandList&& entries);
/// Swap buffers (render frame)
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer);
@@ -132,6 +135,8 @@ public:
void OnCommandListEnd();
+ void TickGPU();
+
private:
/// Pushes a command to be executed by the GPU thread
u64 PushCommand(CommandData&& command_data, bool block = false);
diff --git a/src/video_core/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp
new file mode 100644
index 000000000..42e7d6e4f
--- /dev/null
+++ b/src/video_core/host1x/codecs/codec.cpp
@@ -0,0 +1,310 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <fstream>
+#include <vector>
+#include "common/assert.h"
+#include "common/settings.h"
+#include "video_core/host1x/codecs/codec.h"
+#include "video_core/host1x/codecs/h264.h"
+#include "video_core/host1x/codecs/vp8.h"
+#include "video_core/host1x/codecs/vp9.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/memory_manager.h"
+
+extern "C" {
+#include <libavutil/opt.h>
+#ifdef LIBVA_FOUND
+// for querying VAAPI driver information
+#include <libavutil/hwcontext_vaapi.h>
+#endif
+}
+
+namespace Tegra {
+namespace {
+constexpr AVPixelFormat PREFERRED_GPU_FMT = AV_PIX_FMT_NV12;
+constexpr AVPixelFormat PREFERRED_CPU_FMT = AV_PIX_FMT_YUV420P;
+constexpr std::array PREFERRED_GPU_DECODERS = {
+ AV_HWDEVICE_TYPE_CUDA,
+#ifdef _WIN32
+ AV_HWDEVICE_TYPE_D3D11VA,
+ AV_HWDEVICE_TYPE_DXVA2,
+#elif defined(__unix__)
+ AV_HWDEVICE_TYPE_VAAPI,
+ AV_HWDEVICE_TYPE_VDPAU,
+#endif
+ // last resort for Linux Flatpak (w/ NVIDIA)
+ AV_HWDEVICE_TYPE_VULKAN,
+};
+
+void AVPacketDeleter(AVPacket* ptr) {
+ av_packet_free(&ptr);
+}
+
+using AVPacketPtr = std::unique_ptr<AVPacket, decltype(&AVPacketDeleter)>;
+
+AVPixelFormat GetGpuFormat(AVCodecContext* av_codec_ctx, const AVPixelFormat* pix_fmts) {
+ for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
+ if (*p == av_codec_ctx->pix_fmt) {
+ return av_codec_ctx->pix_fmt;
+ }
+ }
+ LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU");
+ av_buffer_unref(&av_codec_ctx->hw_device_ctx);
+ 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) {
+ av_frame_free(&ptr);
+}
+
+Codec::Codec(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs)
+ : host1x(host1x_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(host1x)),
+ vp8_decoder(std::make_unique<Decoder::VP8>(host1x)),
+ vp9_decoder(std::make_unique<Decoder::VP9>(host1x)) {}
+
+Codec::~Codec() {
+ if (!initialized) {
+ return;
+ }
+ // Free libav memory
+ avcodec_free_context(&av_codec_ctx);
+ av_buffer_unref(&av_gpu_decoder);
+}
+
+bool Codec::CreateGpuAvDevice() {
+ static constexpr auto HW_CONFIG_METHOD = AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX;
+ static const auto supported_contexts = ListSupportedContexts();
+ for (const auto& type : PREFERRED_GPU_DECODERS) {
+ if (std::none_of(supported_contexts.begin(), supported_contexts.end(),
+ [&type](const auto& context) { return context == type; })) {
+ 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 {}",
+ av_hwdevice_get_type_name(type), hwdevice_res);
+ continue;
+ }
+#ifdef LIBVA_FOUND
+ if (type == AV_HWDEVICE_TYPE_VAAPI) {
+ // we need to determine if this is an impersonated VAAPI driver
+ AVHWDeviceContext* hwctx =
+ static_cast<AVHWDeviceContext*>(static_cast<void*>(av_gpu_decoder->data));
+ AVVAAPIDeviceContext* vactx = static_cast<AVVAAPIDeviceContext*>(hwctx->hwctx);
+ const char* vendor_name = vaQueryVendorString(vactx->display);
+ if (strstr(vendor_name, "VDPAU backend")) {
+ // VDPAU impersonated VAAPI impl's are super buggy, we need to skip them
+ LOG_DEBUG(Service_NVDRV, "Skipping vdapu impersonated VAAPI driver");
+ continue;
+ } else {
+ // according to some user testing, certain vaapi driver (Intel?) could be buggy
+ // so let's log the driver name which may help the developers/supporters
+ LOG_DEBUG(Service_NVDRV, "Using VAAPI driver: {}", vendor_name);
+ }
+ }
+#endif
+ for (int i = 0;; i++) {
+ const AVCodecHWConfig* config = avcodec_get_hw_config(av_codec, i);
+ if (!config) {
+ LOG_DEBUG(Service_NVDRV, "{} decoder does not support device type {}.",
+ av_codec->name, av_hwdevice_get_type_name(type));
+ break;
+ }
+ 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;
+ }
+ }
+ }
+ return false;
+}
+
+void Codec::InitializeAvCodecContext() {
+ av_codec_ctx = avcodec_alloc_context3(av_codec);
+ av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
+}
+
+void Codec::InitializeGpuDecoder() {
+ if (!CreateGpuAvDevice()) {
+ av_buffer_unref(&av_gpu_decoder);
+ return;
+ }
+ auto* hw_device_ctx = av_buffer_ref(av_gpu_decoder);
+ ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed");
+ av_codec_ctx->hw_device_ctx = hw_device_ctx;
+ av_codec_ctx->get_format = GetGpuFormat;
+}
+
+void Codec::Initialize() {
+ const AVCodecID codec = [&] {
+ switch (current_codec) {
+ case Host1x::NvdecCommon::VideoCodec::H264:
+ return AV_CODEC_ID_H264;
+ case Host1x::NvdecCommon::VideoCodec::VP8:
+ return AV_CODEC_ID_VP8;
+ case Host1x::NvdecCommon::VideoCodec::VP9:
+ return AV_CODEC_ID_VP9;
+ default:
+ UNIMPLEMENTED_MSG("Unknown codec {}", current_codec);
+ return AV_CODEC_ID_NONE;
+ }
+ }();
+ av_codec = avcodec_find_decoder(codec);
+
+ InitializeAvCodecContext();
+ if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::GPU) {
+ InitializeGpuDecoder();
+ }
+ if (const int res = avcodec_open2(av_codec_ctx, av_codec, nullptr); res < 0) {
+ LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed with result {}", res);
+ avcodec_free_context(&av_codec_ctx);
+ av_buffer_unref(&av_gpu_decoder);
+ return;
+ }
+ if (!av_codec_ctx->hw_device_ctx) {
+ LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding");
+ }
+ initialized = true;
+}
+
+void Codec::SetTargetCodec(Host1x::NvdecCommon::VideoCodec codec) {
+ if (current_codec != codec) {
+ current_codec = codec;
+ LOG_INFO(Service_NVDRV, "NVDEC video codec initialized to {}", GetCurrentCodecName());
+ }
+}
+
+void Codec::Decode() {
+ const bool is_first_frame = !initialized;
+ if (is_first_frame) {
+ Initialize();
+ }
+ if (!initialized) {
+ return;
+ }
+ bool vp9_hidden_frame = false;
+ const auto& frame_data = [&]() {
+ switch (current_codec) {
+ case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
+ return h264_decoder->ComposeFrame(state, is_first_frame);
+ case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
+ return vp8_decoder->ComposeFrame(state);
+ case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
+ vp9_decoder->ComposeFrame(state);
+ vp9_hidden_frame = vp9_decoder->WasFrameHidden();
+ return vp9_decoder->GetFrameBytes();
+ default:
+ ASSERT(false);
+ return std::vector<u8>{};
+ }
+ }();
+ AVPacketPtr packet{av_packet_alloc(), AVPacketDeleter};
+ if (!packet) {
+ LOG_ERROR(Service_NVDRV, "av_packet_alloc failed");
+ return;
+ }
+ packet->data = const_cast<u8*>(frame_data.data());
+ packet->size = static_cast<s32>(frame_data.size());
+ if (const int res = avcodec_send_packet(av_codec_ctx, packet.get()); res != 0) {
+ LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", res);
+ return;
+ }
+ // Only receive/store visible frames
+ if (vp9_hidden_frame) {
+ return;
+ }
+ AVFramePtr initial_frame{av_frame_alloc(), AVFrameDeleter};
+ AVFramePtr final_frame{nullptr, AVFrameDeleter};
+ ASSERT_MSG(initial_frame, "av_frame_alloc initial_frame failed");
+ if (const int ret = avcodec_receive_frame(av_codec_ctx, initial_frame.get()); ret) {
+ LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret);
+ return;
+ }
+ if (initial_frame->width == 0 || initial_frame->height == 0) {
+ LOG_WARNING(Service_NVDRV, "Zero width or height in frame");
+ return;
+ }
+ if (av_codec_ctx->hw_device_ctx) {
+ final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
+ ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed");
+ // Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp
+ // because Intel drivers crash unless using AV_PIX_FMT_NV12
+ final_frame->format = PREFERRED_GPU_FMT;
+ const int ret = av_hwframe_transfer_data(final_frame.get(), initial_frame.get(), 0);
+ ASSERT_MSG(!ret, "av_hwframe_transfer_data error {}", ret);
+ } else {
+ final_frame = std::move(initial_frame);
+ }
+ if (final_frame->format != PREFERRED_CPU_FMT && final_frame->format != PREFERRED_GPU_FMT) {
+ UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format);
+ return;
+ }
+ av_frames.push(std::move(final_frame));
+ if (av_frames.size() > 10) {
+ LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame");
+ av_frames.pop();
+ }
+}
+
+AVFramePtr Codec::GetCurrentFrame() {
+ // Sometimes VIC will request more frames than have been decoded.
+ // in this case, return a nullptr and don't overwrite previous frame data
+ if (av_frames.empty()) {
+ return AVFramePtr{nullptr, AVFrameDeleter};
+ }
+ AVFramePtr frame = std::move(av_frames.front());
+ av_frames.pop();
+ return frame;
+}
+
+Host1x::NvdecCommon::VideoCodec Codec::GetCurrentCodec() const {
+ return current_codec;
+}
+
+std::string_view Codec::GetCurrentCodecName() const {
+ switch (current_codec) {
+ case Host1x::NvdecCommon::VideoCodec::None:
+ return "None";
+ case Host1x::NvdecCommon::VideoCodec::H264:
+ return "H264";
+ case Host1x::NvdecCommon::VideoCodec::VP8:
+ return "VP8";
+ case Host1x::NvdecCommon::VideoCodec::H265:
+ return "H265";
+ case Host1x::NvdecCommon::VideoCodec::VP9:
+ return "VP9";
+ default:
+ return "Unknown";
+ }
+}
+} // namespace Tegra
diff --git a/src/video_core/host1x/codecs/codec.h b/src/video_core/host1x/codecs/codec.h
new file mode 100644
index 000000000..0d45fb7fe
--- /dev/null
+++ b/src/video_core/host1x/codecs/codec.h
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <string_view>
+#include <queue>
+#include "common/common_types.h"
+#include "video_core/host1x/nvdec_common.h"
+
+extern "C" {
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wconversion"
+#endif
+#include <libavcodec/avcodec.h>
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+}
+
+namespace Tegra {
+
+void AVFrameDeleter(AVFrame* ptr);
+using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>;
+
+namespace Decoder {
+class H264;
+class VP8;
+class VP9;
+} // namespace Decoder
+
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
+
+class Codec {
+public:
+ explicit Codec(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs);
+ ~Codec();
+
+ /// Initialize the codec, returning success or failure
+ void Initialize();
+
+ /// Sets NVDEC video stream codec
+ void SetTargetCodec(Host1x::NvdecCommon::VideoCodec codec);
+
+ /// Call decoders to construct headers, decode AVFrame with ffmpeg
+ void Decode();
+
+ /// Returns next decoded frame
+ [[nodiscard]] AVFramePtr GetCurrentFrame();
+
+ /// Returns the value of current_codec
+ [[nodiscard]] Host1x::NvdecCommon::VideoCodec GetCurrentCodec() const;
+
+ /// Return name of the current codec
+ [[nodiscard]] std::string_view GetCurrentCodecName() const;
+
+private:
+ void InitializeAvCodecContext();
+
+ void InitializeGpuDecoder();
+
+ bool CreateGpuAvDevice();
+
+ bool initialized{};
+ Host1x::NvdecCommon::VideoCodec current_codec{Host1x::NvdecCommon::VideoCodec::None};
+
+ const AVCodec* av_codec{nullptr};
+ AVCodecContext* av_codec_ctx{nullptr};
+ AVBufferRef* av_gpu_decoder{nullptr};
+
+ Host1x::Host1x& host1x;
+ const Host1x::NvdecCommon::NvdecRegisters& state;
+ std::unique_ptr<Decoder::H264> h264_decoder;
+ std::unique_ptr<Decoder::VP8> vp8_decoder;
+ std::unique_ptr<Decoder::VP9> vp9_decoder;
+
+ std::queue<AVFramePtr> av_frames{};
+};
+
+} // namespace Tegra
diff --git a/src/video_core/host1x/codecs/h264.cpp b/src/video_core/host1x/codecs/h264.cpp
new file mode 100644
index 000000000..e87bd65fa
--- /dev/null
+++ b/src/video_core/host1x/codecs/h264.cpp
@@ -0,0 +1,278 @@
+// SPDX-FileCopyrightText: Ryujinx Team and Contributors
+// SPDX-License-Identifier: MIT
+
+#include <array>
+#include <bit>
+
+#include "common/settings.h"
+#include "video_core/host1x/codecs/h264.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra::Decoder {
+namespace {
+// ZigZag LUTs from libavcodec.
+constexpr std::array<u8, 64> zig_zag_direct{
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48,
+ 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23,
+ 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
+};
+
+constexpr std::array<u8, 16> zig_zag_scan{
+ 0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4, 1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4,
+ 1 + 2 * 4, 0 + 3 * 4, 1 + 3 * 4, 2 + 2 * 4, 3 + 1 * 4, 3 + 2 * 4, 2 + 3 * 4, 3 + 3 * 4,
+};
+} // Anonymous namespace
+
+H264::H264(Host1x::Host1x& host1x_) : host1x{host1x_} {}
+
+H264::~H264() = default;
+
+const std::vector<u8>& H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state,
+ bool is_first_frame) {
+ H264DecoderContext context;
+ host1x.MemoryManager().ReadBlock(state.picture_info_offset, &context,
+ sizeof(H264DecoderContext));
+
+ const s64 frame_number = context.h264_parameter_set.frame_number.Value();
+ if (!is_first_frame && frame_number != 0) {
+ frame.resize(context.stream_len);
+ host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size());
+ return frame;
+ }
+
+ // Encode header
+ H264BitWriter writer{};
+ writer.WriteU(1, 24);
+ writer.WriteU(0, 1);
+ writer.WriteU(3, 2);
+ writer.WriteU(7, 5);
+ writer.WriteU(100, 8);
+ writer.WriteU(0, 8);
+ writer.WriteU(31, 8);
+ writer.WriteUe(0);
+ const u32 chroma_format_idc =
+ static_cast<u32>(context.h264_parameter_set.chroma_format_idc.Value());
+ writer.WriteUe(chroma_format_idc);
+ if (chroma_format_idc == 3) {
+ writer.WriteBit(false);
+ }
+
+ writer.WriteUe(0);
+ writer.WriteUe(0);
+ writer.WriteBit(false); // QpprimeYZeroTransformBypassFlag
+ writer.WriteBit(false); // Scaling matrix present flag
+
+ writer.WriteUe(static_cast<u32>(context.h264_parameter_set.log2_max_frame_num_minus4.Value()));
+
+ const auto order_cnt_type =
+ static_cast<u32>(context.h264_parameter_set.pic_order_cnt_type.Value());
+ writer.WriteUe(order_cnt_type);
+ if (order_cnt_type == 0) {
+ writer.WriteUe(context.h264_parameter_set.log2_max_pic_order_cnt_lsb_minus4);
+ } else if (order_cnt_type == 1) {
+ writer.WriteBit(context.h264_parameter_set.delta_pic_order_always_zero_flag != 0);
+
+ writer.WriteSe(0);
+ writer.WriteSe(0);
+ writer.WriteUe(0);
+ }
+
+ const s32 pic_height = context.h264_parameter_set.frame_height_in_map_units /
+ (context.h264_parameter_set.frame_mbs_only_flag ? 1 : 2);
+
+ // TODO (ameerj): Where do we get this number, it seems to be particular for each stream
+ const auto nvdec_decoding = Settings::values.nvdec_emulation.GetValue();
+ const bool uses_gpu_decoding = nvdec_decoding == Settings::NvdecEmulation::GPU;
+ const u32 max_num_ref_frames = uses_gpu_decoding ? 6u : 16u;
+ writer.WriteUe(max_num_ref_frames);
+ writer.WriteBit(false);
+ writer.WriteUe(context.h264_parameter_set.pic_width_in_mbs - 1);
+ writer.WriteUe(pic_height - 1);
+ writer.WriteBit(context.h264_parameter_set.frame_mbs_only_flag != 0);
+
+ if (!context.h264_parameter_set.frame_mbs_only_flag) {
+ writer.WriteBit(context.h264_parameter_set.flags.mbaff_frame.Value() != 0);
+ }
+
+ writer.WriteBit(context.h264_parameter_set.flags.direct_8x8_inference.Value() != 0);
+ writer.WriteBit(false); // Frame cropping flag
+ writer.WriteBit(false); // VUI parameter present flag
+
+ writer.End();
+
+ // H264 PPS
+ writer.WriteU(1, 24);
+ writer.WriteU(0, 1);
+ writer.WriteU(3, 2);
+ writer.WriteU(8, 5);
+
+ writer.WriteUe(0);
+ writer.WriteUe(0);
+
+ writer.WriteBit(context.h264_parameter_set.entropy_coding_mode_flag != 0);
+ writer.WriteBit(false);
+ writer.WriteUe(0);
+ writer.WriteUe(context.h264_parameter_set.num_refidx_l0_default_active);
+ writer.WriteUe(context.h264_parameter_set.num_refidx_l1_default_active);
+ writer.WriteBit(context.h264_parameter_set.flags.weighted_pred.Value() != 0);
+ writer.WriteU(static_cast<s32>(context.h264_parameter_set.weighted_bipred_idc.Value()), 2);
+ s32 pic_init_qp = static_cast<s32>(context.h264_parameter_set.pic_init_qp_minus26.Value());
+ writer.WriteSe(pic_init_qp);
+ writer.WriteSe(0);
+ s32 chroma_qp_index_offset =
+ static_cast<s32>(context.h264_parameter_set.chroma_qp_index_offset.Value());
+
+ writer.WriteSe(chroma_qp_index_offset);
+ writer.WriteBit(context.h264_parameter_set.deblocking_filter_control_present_flag != 0);
+ writer.WriteBit(context.h264_parameter_set.flags.constrained_intra_pred.Value() != 0);
+ writer.WriteBit(context.h264_parameter_set.redundant_pic_cnt_present_flag != 0);
+ writer.WriteBit(context.h264_parameter_set.transform_8x8_mode_flag != 0);
+
+ writer.WriteBit(true);
+
+ for (s32 index = 0; index < 6; index++) {
+ writer.WriteBit(true);
+ std::span<const u8> matrix{context.weight_scale};
+ writer.WriteScalingList(matrix, index * 16, 16);
+ }
+
+ if (context.h264_parameter_set.transform_8x8_mode_flag) {
+ for (s32 index = 0; index < 2; index++) {
+ writer.WriteBit(true);
+ std::span<const u8> matrix{context.weight_scale_8x8};
+ writer.WriteScalingList(matrix, index * 64, 64);
+ }
+ }
+
+ s32 chroma_qp_index_offset2 =
+ static_cast<s32>(context.h264_parameter_set.second_chroma_qp_index_offset.Value());
+
+ writer.WriteSe(chroma_qp_index_offset2);
+
+ writer.End();
+
+ const auto& encoded_header = writer.GetByteArray();
+ frame.resize(encoded_header.size() + context.stream_len);
+ std::memcpy(frame.data(), encoded_header.data(), encoded_header.size());
+
+ host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset,
+ frame.data() + encoded_header.size(), context.stream_len);
+
+ return frame;
+}
+
+H264BitWriter::H264BitWriter() = default;
+
+H264BitWriter::~H264BitWriter() = default;
+
+void H264BitWriter::WriteU(s32 value, s32 value_sz) {
+ WriteBits(value, value_sz);
+}
+
+void H264BitWriter::WriteSe(s32 value) {
+ WriteExpGolombCodedInt(value);
+}
+
+void H264BitWriter::WriteUe(u32 value) {
+ WriteExpGolombCodedUInt(value);
+}
+
+void H264BitWriter::End() {
+ WriteBit(true);
+ Flush();
+}
+
+void H264BitWriter::WriteBit(bool state) {
+ WriteBits(state ? 1 : 0, 1);
+}
+
+void H264BitWriter::WriteScalingList(std::span<const u8> list, s32 start, s32 count) {
+ std::vector<u8> scan(count);
+ if (count == 16) {
+ std::memcpy(scan.data(), zig_zag_scan.data(), scan.size());
+ } else {
+ std::memcpy(scan.data(), zig_zag_direct.data(), scan.size());
+ }
+ u8 last_scale = 8;
+
+ for (s32 index = 0; index < count; index++) {
+ const u8 value = list[start + scan[index]];
+ const s32 delta_scale = static_cast<s32>(value - last_scale);
+
+ WriteSe(delta_scale);
+
+ last_scale = value;
+ }
+}
+
+std::vector<u8>& H264BitWriter::GetByteArray() {
+ return byte_array;
+}
+
+const std::vector<u8>& H264BitWriter::GetByteArray() const {
+ return byte_array;
+}
+
+void H264BitWriter::WriteBits(s32 value, s32 bit_count) {
+ s32 value_pos = 0;
+
+ s32 remaining = bit_count;
+
+ while (remaining > 0) {
+ s32 copy_size = remaining;
+
+ const s32 free_bits = GetFreeBufferBits();
+
+ if (copy_size > free_bits) {
+ copy_size = free_bits;
+ }
+
+ const s32 mask = (1 << copy_size) - 1;
+
+ const s32 src_shift = (bit_count - value_pos) - copy_size;
+ const s32 dst_shift = (buffer_size - buffer_pos) - copy_size;
+
+ buffer |= ((value >> src_shift) & mask) << dst_shift;
+
+ value_pos += copy_size;
+ buffer_pos += copy_size;
+ remaining -= copy_size;
+ }
+}
+
+void H264BitWriter::WriteExpGolombCodedInt(s32 value) {
+ const s32 sign = value <= 0 ? 0 : 1;
+ if (value < 0) {
+ value = -value;
+ }
+ value = (value << 1) - sign;
+ WriteExpGolombCodedUInt(value);
+}
+
+void H264BitWriter::WriteExpGolombCodedUInt(u32 value) {
+ const s32 size = 32 - std::countl_zero(value + 1);
+ WriteBits(1, size);
+
+ value -= (1U << (size - 1)) - 1;
+ WriteBits(static_cast<s32>(value), size - 1);
+}
+
+s32 H264BitWriter::GetFreeBufferBits() {
+ if (buffer_pos == buffer_size) {
+ Flush();
+ }
+
+ return buffer_size - buffer_pos;
+}
+
+void H264BitWriter::Flush() {
+ if (buffer_pos == 0) {
+ return;
+ }
+ byte_array.push_back(static_cast<u8>(buffer));
+
+ buffer = 0;
+ buffer_pos = 0;
+}
+} // namespace Tegra::Decoder
diff --git a/src/video_core/host1x/codecs/h264.h b/src/video_core/host1x/codecs/h264.h
new file mode 100644
index 000000000..5cc86454e
--- /dev/null
+++ b/src/video_core/host1x/codecs/h264.h
@@ -0,0 +1,177 @@
+// SPDX-FileCopyrightText: Ryujinx Team and Contributors
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <span>
+#include <vector>
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "video_core/host1x/nvdec_common.h"
+
+namespace Tegra {
+
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
+
+namespace Decoder {
+
+class H264BitWriter {
+public:
+ H264BitWriter();
+ ~H264BitWriter();
+
+ /// The following Write methods are based on clause 9.1 in the H.264 specification.
+ /// WriteSe and WriteUe write in the Exp-Golomb-coded syntax
+ void WriteU(s32 value, s32 value_sz);
+ void WriteSe(s32 value);
+ void WriteUe(u32 value);
+
+ /// Finalize the bitstream
+ void End();
+
+ /// append a bit to the stream, equivalent value to the state parameter
+ void WriteBit(bool state);
+
+ /// Based on section 7.3.2.1.1.1 and Table 7-4 in the H.264 specification
+ /// Writes the scaling matrices of the sream
+ void WriteScalingList(std::span<const u8> list, s32 start, s32 count);
+
+ /// Return the bitstream as a vector.
+ [[nodiscard]] std::vector<u8>& GetByteArray();
+ [[nodiscard]] const std::vector<u8>& GetByteArray() const;
+
+private:
+ void WriteBits(s32 value, s32 bit_count);
+ void WriteExpGolombCodedInt(s32 value);
+ void WriteExpGolombCodedUInt(u32 value);
+ [[nodiscard]] s32 GetFreeBufferBits();
+ void Flush();
+
+ s32 buffer_size{8};
+
+ s32 buffer{};
+ s32 buffer_pos{};
+ std::vector<u8> byte_array;
+};
+
+class H264 {
+public:
+ explicit H264(Host1x::Host1x& host1x);
+ ~H264();
+
+ /// Compose the H264 frame for FFmpeg decoding
+ [[nodiscard]] const std::vector<u8>& ComposeFrame(
+ const Host1x::NvdecCommon::NvdecRegisters& state, bool is_first_frame = false);
+
+private:
+ std::vector<u8> frame;
+ Host1x::Host1x& host1x;
+
+ struct H264ParameterSet {
+ s32 log2_max_pic_order_cnt_lsb_minus4; ///< 0x00
+ s32 delta_pic_order_always_zero_flag; ///< 0x04
+ s32 frame_mbs_only_flag; ///< 0x08
+ u32 pic_width_in_mbs; ///< 0x0C
+ u32 frame_height_in_map_units; ///< 0x10
+ union { ///< 0x14
+ BitField<0, 2, u32> tile_format;
+ BitField<2, 3, u32> gob_height;
+ };
+ u32 entropy_coding_mode_flag; ///< 0x18
+ s32 pic_order_present_flag; ///< 0x1C
+ s32 num_refidx_l0_default_active; ///< 0x20
+ s32 num_refidx_l1_default_active; ///< 0x24
+ s32 deblocking_filter_control_present_flag; ///< 0x28
+ s32 redundant_pic_cnt_present_flag; ///< 0x2C
+ u32 transform_8x8_mode_flag; ///< 0x30
+ u32 pitch_luma; ///< 0x34
+ u32 pitch_chroma; ///< 0x38
+ u32 luma_top_offset; ///< 0x3C
+ u32 luma_bot_offset; ///< 0x40
+ u32 luma_frame_offset; ///< 0x44
+ u32 chroma_top_offset; ///< 0x48
+ u32 chroma_bot_offset; ///< 0x4C
+ u32 chroma_frame_offset; ///< 0x50
+ u32 hist_buffer_size; ///< 0x54
+ union { ///< 0x58
+ union {
+ BitField<0, 1, u64> mbaff_frame;
+ BitField<1, 1, u64> direct_8x8_inference;
+ BitField<2, 1, u64> weighted_pred;
+ BitField<3, 1, u64> constrained_intra_pred;
+ BitField<4, 1, u64> ref_pic;
+ BitField<5, 1, u64> field_pic;
+ BitField<6, 1, u64> bottom_field;
+ BitField<7, 1, u64> second_field;
+ } flags;
+ BitField<8, 4, u64> log2_max_frame_num_minus4;
+ BitField<12, 2, u64> chroma_format_idc;
+ BitField<14, 2, u64> pic_order_cnt_type;
+ BitField<16, 6, s64> pic_init_qp_minus26;
+ BitField<22, 5, s64> chroma_qp_index_offset;
+ BitField<27, 5, s64> second_chroma_qp_index_offset;
+ BitField<32, 2, u64> weighted_bipred_idc;
+ BitField<34, 7, u64> curr_pic_idx;
+ BitField<41, 5, u64> curr_col_idx;
+ BitField<46, 16, u64> frame_number;
+ BitField<62, 1, u64> frame_surfaces;
+ BitField<63, 1, u64> output_memory_layout;
+ };
+ };
+ static_assert(sizeof(H264ParameterSet) == 0x60, "H264ParameterSet is an invalid size");
+
+ struct H264DecoderContext {
+ INSERT_PADDING_WORDS_NOINIT(18); ///< 0x0000
+ u32 stream_len; ///< 0x0048
+ INSERT_PADDING_WORDS_NOINIT(3); ///< 0x004C
+ H264ParameterSet h264_parameter_set; ///< 0x0058
+ INSERT_PADDING_WORDS_NOINIT(66); ///< 0x00B8
+ std::array<u8, 0x60> weight_scale; ///< 0x01C0
+ std::array<u8, 0x80> weight_scale_8x8; ///< 0x0220
+ };
+ static_assert(sizeof(H264DecoderContext) == 0x2A0, "H264DecoderContext is an invalid size");
+
+#define ASSERT_POSITION(field_name, position) \
+ static_assert(offsetof(H264ParameterSet, field_name) == position, \
+ "Field " #field_name " has invalid position")
+
+ ASSERT_POSITION(log2_max_pic_order_cnt_lsb_minus4, 0x00);
+ ASSERT_POSITION(delta_pic_order_always_zero_flag, 0x04);
+ ASSERT_POSITION(frame_mbs_only_flag, 0x08);
+ ASSERT_POSITION(pic_width_in_mbs, 0x0C);
+ ASSERT_POSITION(frame_height_in_map_units, 0x10);
+ ASSERT_POSITION(tile_format, 0x14);
+ ASSERT_POSITION(entropy_coding_mode_flag, 0x18);
+ ASSERT_POSITION(pic_order_present_flag, 0x1C);
+ ASSERT_POSITION(num_refidx_l0_default_active, 0x20);
+ ASSERT_POSITION(num_refidx_l1_default_active, 0x24);
+ ASSERT_POSITION(deblocking_filter_control_present_flag, 0x28);
+ ASSERT_POSITION(redundant_pic_cnt_present_flag, 0x2C);
+ ASSERT_POSITION(transform_8x8_mode_flag, 0x30);
+ ASSERT_POSITION(pitch_luma, 0x34);
+ ASSERT_POSITION(pitch_chroma, 0x38);
+ ASSERT_POSITION(luma_top_offset, 0x3C);
+ ASSERT_POSITION(luma_bot_offset, 0x40);
+ ASSERT_POSITION(luma_frame_offset, 0x44);
+ ASSERT_POSITION(chroma_top_offset, 0x48);
+ ASSERT_POSITION(chroma_bot_offset, 0x4C);
+ ASSERT_POSITION(chroma_frame_offset, 0x50);
+ ASSERT_POSITION(hist_buffer_size, 0x54);
+ ASSERT_POSITION(flags, 0x58);
+#undef ASSERT_POSITION
+
+#define ASSERT_POSITION(field_name, position) \
+ static_assert(offsetof(H264DecoderContext, field_name) == position, \
+ "Field " #field_name " has invalid position")
+
+ ASSERT_POSITION(stream_len, 0x48);
+ ASSERT_POSITION(h264_parameter_set, 0x58);
+ ASSERT_POSITION(weight_scale, 0x1C0);
+#undef ASSERT_POSITION
+};
+
+} // namespace Decoder
+} // namespace Tegra
diff --git a/src/video_core/host1x/codecs/vp8.cpp b/src/video_core/host1x/codecs/vp8.cpp
new file mode 100644
index 000000000..28fb12cb8
--- /dev/null
+++ b/src/video_core/host1x/codecs/vp8.cpp
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <vector>
+
+#include "video_core/host1x/codecs/vp8.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra::Decoder {
+VP8::VP8(Host1x::Host1x& host1x_) : host1x{host1x_} {}
+
+VP8::~VP8() = default;
+
+const std::vector<u8>& VP8::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state) {
+ VP8PictureInfo info;
+ host1x.MemoryManager().ReadBlock(state.picture_info_offset, &info, sizeof(VP8PictureInfo));
+
+ const bool is_key_frame = info.key_frame == 1u;
+ const auto bitstream_size = static_cast<size_t>(info.vld_buffer_size);
+ const size_t header_size = is_key_frame ? 10u : 3u;
+ frame.resize(header_size + bitstream_size);
+
+ // Based on page 30 of the VP8 specification.
+ // https://datatracker.ietf.org/doc/rfc6386/
+ frame[0] = is_key_frame ? 0u : 1u; // 1-bit frame type (0: keyframe, 1: interframes).
+ frame[0] |= static_cast<u8>((info.version & 7u) << 1u); // 3-bit version number
+ frame[0] |= static_cast<u8>(1u << 4u); // 1-bit show_frame flag
+
+ // The next 19-bits are the first partition size
+ frame[0] |= static_cast<u8>((info.first_part_size & 7u) << 5u);
+ frame[1] = static_cast<u8>((info.first_part_size & 0x7f8u) >> 3u);
+ frame[2] = static_cast<u8>((info.first_part_size & 0x7f800u) >> 11u);
+
+ if (is_key_frame) {
+ frame[3] = 0x9du;
+ frame[4] = 0x01u;
+ frame[5] = 0x2au;
+ // TODO(ameerj): Horizontal/Vertical Scale
+ // 16 bits: (2 bits Horizontal Scale << 14) | Width (14 bits)
+ frame[6] = static_cast<u8>(info.frame_width & 0xff);
+ frame[7] = static_cast<u8>(((info.frame_width >> 8) & 0x3f));
+ // 16 bits:(2 bits Vertical Scale << 14) | Height (14 bits)
+ frame[8] = static_cast<u8>(info.frame_height & 0xff);
+ frame[9] = static_cast<u8>(((info.frame_height >> 8) & 0x3f));
+ }
+ const u64 bitstream_offset = state.frame_bitstream_offset;
+ host1x.MemoryManager().ReadBlock(bitstream_offset, frame.data() + header_size, bitstream_size);
+
+ return frame;
+}
+
+} // namespace Tegra::Decoder
diff --git a/src/video_core/host1x/codecs/vp8.h b/src/video_core/host1x/codecs/vp8.h
new file mode 100644
index 000000000..5bf07ecab
--- /dev/null
+++ b/src/video_core/host1x/codecs/vp8.h
@@ -0,0 +1,78 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "video_core/host1x/nvdec_common.h"
+
+namespace Tegra {
+
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
+
+namespace Decoder {
+
+class VP8 {
+public:
+ explicit VP8(Host1x::Host1x& host1x);
+ ~VP8();
+
+ /// Compose the VP8 frame for FFmpeg decoding
+ [[nodiscard]] const std::vector<u8>& ComposeFrame(
+ const Host1x::NvdecCommon::NvdecRegisters& state);
+
+private:
+ std::vector<u8> frame;
+ Host1x::Host1x& host1x;
+
+ struct VP8PictureInfo {
+ INSERT_PADDING_WORDS_NOINIT(14);
+ u16 frame_width; // actual frame width
+ u16 frame_height; // actual frame height
+ u8 key_frame;
+ u8 version;
+ union {
+ u8 raw;
+ BitField<0, 2, u8> tile_format;
+ BitField<2, 3, u8> gob_height;
+ BitField<5, 3, u8> reserverd_surface_format;
+ };
+ u8 error_conceal_on; // 1: error conceal on; 0: off
+ u32 first_part_size; // the size of first partition(frame header and mb header partition)
+ u32 hist_buffer_size; // in units of 256
+ u32 vld_buffer_size; // in units of 1
+ // Current frame buffers
+ std::array<u32, 2> frame_stride; // [y_c]
+ u32 luma_top_offset; // offset of luma top field in units of 256
+ u32 luma_bot_offset; // offset of luma bottom field in units of 256
+ u32 luma_frame_offset; // offset of luma frame in units of 256
+ u32 chroma_top_offset; // offset of chroma top field in units of 256
+ u32 chroma_bot_offset; // offset of chroma bottom field in units of 256
+ u32 chroma_frame_offset; // offset of chroma frame in units of 256
+
+ INSERT_PADDING_BYTES_NOINIT(0x1c); // NvdecDisplayParams
+
+ // Decode picture buffer related
+ s8 current_output_memory_layout;
+ // output NV12/NV24 setting. index 0: golden; 1: altref; 2: last
+ std::array<s8, 3> output_memory_layout;
+
+ u8 segmentation_feature_data_update;
+ INSERT_PADDING_BYTES_NOINIT(3);
+
+ // ucode return result
+ u32 result_value;
+ std::array<u32, 8> partition_offset;
+ INSERT_PADDING_WORDS_NOINIT(3);
+ };
+ static_assert(sizeof(VP8PictureInfo) == 0xc0, "PictureInfo is an invalid size");
+};
+
+} // namespace Decoder
+} // namespace Tegra
diff --git a/src/video_core/host1x/codecs/vp9.cpp b/src/video_core/host1x/codecs/vp9.cpp
new file mode 100644
index 000000000..cf40c9012
--- /dev/null
+++ b/src/video_core/host1x/codecs/vp9.cpp
@@ -0,0 +1,947 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm> // for std::copy
+#include <numeric>
+#include "common/assert.h"
+#include "video_core/host1x/codecs/vp9.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra::Decoder {
+namespace {
+constexpr u32 diff_update_probability = 252;
+constexpr u32 frame_sync_code = 0x498342;
+
+// Default compressed header probabilities once frame context resets
+constexpr Vp9EntropyProbs default_probs{
+ .y_mode_prob{
+ 65, 32, 18, 144, 162, 194, 41, 51, 98, 132, 68, 18, 165, 217, 196, 45, 40, 78,
+ 173, 80, 19, 176, 240, 193, 64, 35, 46, 221, 135, 38, 194, 248, 121, 96, 85, 29,
+ },
+ .partition_prob{
+ 199, 122, 141, 0, 147, 63, 159, 0, 148, 133, 118, 0, 121, 104, 114, 0,
+ 174, 73, 87, 0, 92, 41, 83, 0, 82, 99, 50, 0, 53, 39, 39, 0,
+ 177, 58, 59, 0, 68, 26, 63, 0, 52, 79, 25, 0, 17, 14, 12, 0,
+ 222, 34, 30, 0, 72, 16, 44, 0, 58, 32, 12, 0, 10, 7, 6, 0,
+ },
+ .coef_probs{
+ 195, 29, 183, 84, 49, 136, 8, 42, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 31, 107, 169, 35, 99, 159, 17, 82, 140, 8, 66, 114, 2, 44, 76, 1, 19, 32,
+ 40, 132, 201, 29, 114, 187, 13, 91, 157, 7, 75, 127, 3, 58, 95, 1, 28, 47,
+ 69, 142, 221, 42, 122, 201, 15, 91, 159, 6, 67, 121, 1, 42, 77, 1, 17, 31,
+ 102, 148, 228, 67, 117, 204, 17, 82, 154, 6, 59, 114, 2, 39, 75, 1, 15, 29,
+ 156, 57, 233, 119, 57, 212, 58, 48, 163, 29, 40, 124, 12, 30, 81, 3, 12, 31,
+ 191, 107, 226, 124, 117, 204, 25, 99, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 29, 148, 210, 37, 126, 194, 8, 93, 157, 2, 68, 118, 1, 39, 69, 1, 17, 33,
+ 41, 151, 213, 27, 123, 193, 3, 82, 144, 1, 58, 105, 1, 32, 60, 1, 13, 26,
+ 59, 159, 220, 23, 126, 198, 4, 88, 151, 1, 66, 114, 1, 38, 71, 1, 18, 34,
+ 114, 136, 232, 51, 114, 207, 11, 83, 155, 3, 56, 105, 1, 33, 65, 1, 17, 34,
+ 149, 65, 234, 121, 57, 215, 61, 49, 166, 28, 36, 114, 12, 25, 76, 3, 16, 42,
+ 214, 49, 220, 132, 63, 188, 42, 65, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 85, 137, 221, 104, 131, 216, 49, 111, 192, 21, 87, 155, 2, 49, 87, 1, 16, 28,
+ 89, 163, 230, 90, 137, 220, 29, 100, 183, 10, 70, 135, 2, 42, 81, 1, 17, 33,
+ 108, 167, 237, 55, 133, 222, 15, 97, 179, 4, 72, 135, 1, 45, 85, 1, 19, 38,
+ 124, 146, 240, 66, 124, 224, 17, 88, 175, 4, 58, 122, 1, 36, 75, 1, 18, 37,
+ 141, 79, 241, 126, 70, 227, 66, 58, 182, 30, 44, 136, 12, 34, 96, 2, 20, 47,
+ 229, 99, 249, 143, 111, 235, 46, 109, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 82, 158, 236, 94, 146, 224, 25, 117, 191, 9, 87, 149, 3, 56, 99, 1, 33, 57,
+ 83, 167, 237, 68, 145, 222, 10, 103, 177, 2, 72, 131, 1, 41, 79, 1, 20, 39,
+ 99, 167, 239, 47, 141, 224, 10, 104, 178, 2, 73, 133, 1, 44, 85, 1, 22, 47,
+ 127, 145, 243, 71, 129, 228, 17, 93, 177, 3, 61, 124, 1, 41, 84, 1, 21, 52,
+ 157, 78, 244, 140, 72, 231, 69, 58, 184, 31, 44, 137, 14, 38, 105, 8, 23, 61,
+ 125, 34, 187, 52, 41, 133, 6, 31, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 37, 109, 153, 51, 102, 147, 23, 87, 128, 8, 67, 101, 1, 41, 63, 1, 19, 29,
+ 31, 154, 185, 17, 127, 175, 6, 96, 145, 2, 73, 114, 1, 51, 82, 1, 28, 45,
+ 23, 163, 200, 10, 131, 185, 2, 93, 148, 1, 67, 111, 1, 41, 69, 1, 14, 24,
+ 29, 176, 217, 12, 145, 201, 3, 101, 156, 1, 69, 111, 1, 39, 63, 1, 14, 23,
+ 57, 192, 233, 25, 154, 215, 6, 109, 167, 3, 78, 118, 1, 48, 69, 1, 21, 29,
+ 202, 105, 245, 108, 106, 216, 18, 90, 144, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 33, 172, 219, 64, 149, 206, 14, 117, 177, 5, 90, 141, 2, 61, 95, 1, 37, 57,
+ 33, 179, 220, 11, 140, 198, 1, 89, 148, 1, 60, 104, 1, 33, 57, 1, 12, 21,
+ 30, 181, 221, 8, 141, 198, 1, 87, 145, 1, 58, 100, 1, 31, 55, 1, 12, 20,
+ 32, 186, 224, 7, 142, 198, 1, 86, 143, 1, 58, 100, 1, 31, 55, 1, 12, 22,
+ 57, 192, 227, 20, 143, 204, 3, 96, 154, 1, 68, 112, 1, 42, 69, 1, 19, 32,
+ 212, 35, 215, 113, 47, 169, 29, 48, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 74, 129, 203, 106, 120, 203, 49, 107, 178, 19, 84, 144, 4, 50, 84, 1, 15, 25,
+ 71, 172, 217, 44, 141, 209, 15, 102, 173, 6, 76, 133, 2, 51, 89, 1, 24, 42,
+ 64, 185, 231, 31, 148, 216, 8, 103, 175, 3, 74, 131, 1, 46, 81, 1, 18, 30,
+ 65, 196, 235, 25, 157, 221, 5, 105, 174, 1, 67, 120, 1, 38, 69, 1, 15, 30,
+ 65, 204, 238, 30, 156, 224, 7, 107, 177, 2, 70, 124, 1, 42, 73, 1, 18, 34,
+ 225, 86, 251, 144, 104, 235, 42, 99, 181, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 85, 175, 239, 112, 165, 229, 29, 136, 200, 12, 103, 162, 6, 77, 123, 2, 53, 84,
+ 75, 183, 239, 30, 155, 221, 3, 106, 171, 1, 74, 128, 1, 44, 76, 1, 17, 28,
+ 73, 185, 240, 27, 159, 222, 2, 107, 172, 1, 75, 127, 1, 42, 73, 1, 17, 29,
+ 62, 190, 238, 21, 159, 222, 2, 107, 172, 1, 72, 122, 1, 40, 71, 1, 18, 32,
+ 61, 199, 240, 27, 161, 226, 4, 113, 180, 1, 76, 129, 1, 46, 80, 1, 23, 41,
+ 7, 27, 153, 5, 30, 95, 1, 16, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 50, 75, 127, 57, 75, 124, 27, 67, 108, 10, 54, 86, 1, 33, 52, 1, 12, 18,
+ 43, 125, 151, 26, 108, 148, 7, 83, 122, 2, 59, 89, 1, 38, 60, 1, 17, 27,
+ 23, 144, 163, 13, 112, 154, 2, 75, 117, 1, 50, 81, 1, 31, 51, 1, 14, 23,
+ 18, 162, 185, 6, 123, 171, 1, 78, 125, 1, 51, 86, 1, 31, 54, 1, 14, 23,
+ 15, 199, 227, 3, 150, 204, 1, 91, 146, 1, 55, 95, 1, 30, 53, 1, 11, 20,
+ 19, 55, 240, 19, 59, 196, 3, 52, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 41, 166, 207, 104, 153, 199, 31, 123, 181, 14, 101, 152, 5, 72, 106, 1, 36, 52,
+ 35, 176, 211, 12, 131, 190, 2, 88, 144, 1, 60, 101, 1, 36, 60, 1, 16, 28,
+ 28, 183, 213, 8, 134, 191, 1, 86, 142, 1, 56, 96, 1, 30, 53, 1, 12, 20,
+ 20, 190, 215, 4, 135, 192, 1, 84, 139, 1, 53, 91, 1, 28, 49, 1, 11, 20,
+ 13, 196, 216, 2, 137, 192, 1, 86, 143, 1, 57, 99, 1, 32, 56, 1, 13, 24,
+ 211, 29, 217, 96, 47, 156, 22, 43, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 78, 120, 193, 111, 116, 186, 46, 102, 164, 15, 80, 128, 2, 49, 76, 1, 18, 28,
+ 71, 161, 203, 42, 132, 192, 10, 98, 150, 3, 69, 109, 1, 44, 70, 1, 18, 29,
+ 57, 186, 211, 30, 140, 196, 4, 93, 146, 1, 62, 102, 1, 38, 65, 1, 16, 27,
+ 47, 199, 217, 14, 145, 196, 1, 88, 142, 1, 57, 98, 1, 36, 62, 1, 15, 26,
+ 26, 219, 229, 5, 155, 207, 1, 94, 151, 1, 60, 104, 1, 36, 62, 1, 16, 28,
+ 233, 29, 248, 146, 47, 220, 43, 52, 140, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 100, 163, 232, 179, 161, 222, 63, 142, 204, 37, 113, 174, 26, 89, 137, 18, 68, 97,
+ 85, 181, 230, 32, 146, 209, 7, 100, 164, 3, 71, 121, 1, 45, 77, 1, 18, 30,
+ 65, 187, 230, 20, 148, 207, 2, 97, 159, 1, 68, 116, 1, 40, 70, 1, 14, 29,
+ 40, 194, 227, 8, 147, 204, 1, 94, 155, 1, 65, 112, 1, 39, 66, 1, 14, 26,
+ 16, 208, 228, 3, 151, 207, 1, 98, 160, 1, 67, 117, 1, 41, 74, 1, 17, 31,
+ 17, 38, 140, 7, 34, 80, 1, 17, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 37, 75, 128, 41, 76, 128, 26, 66, 116, 12, 52, 94, 2, 32, 55, 1, 10, 16,
+ 50, 127, 154, 37, 109, 152, 16, 82, 121, 5, 59, 85, 1, 35, 54, 1, 13, 20,
+ 40, 142, 167, 17, 110, 157, 2, 71, 112, 1, 44, 72, 1, 27, 45, 1, 11, 17,
+ 30, 175, 188, 9, 124, 169, 1, 74, 116, 1, 48, 78, 1, 30, 49, 1, 11, 18,
+ 10, 222, 223, 2, 150, 194, 1, 83, 128, 1, 48, 79, 1, 27, 45, 1, 11, 17,
+ 36, 41, 235, 29, 36, 193, 10, 27, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 85, 165, 222, 177, 162, 215, 110, 135, 195, 57, 113, 168, 23, 83, 120, 10, 49, 61,
+ 85, 190, 223, 36, 139, 200, 5, 90, 146, 1, 60, 103, 1, 38, 65, 1, 18, 30,
+ 72, 202, 223, 23, 141, 199, 2, 86, 140, 1, 56, 97, 1, 36, 61, 1, 16, 27,
+ 55, 218, 225, 13, 145, 200, 1, 86, 141, 1, 57, 99, 1, 35, 61, 1, 13, 22,
+ 15, 235, 212, 1, 132, 184, 1, 84, 139, 1, 57, 97, 1, 34, 56, 1, 14, 23,
+ 181, 21, 201, 61, 37, 123, 10, 38, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 47, 106, 172, 95, 104, 173, 42, 93, 159, 18, 77, 131, 4, 50, 81, 1, 17, 23,
+ 62, 147, 199, 44, 130, 189, 28, 102, 154, 18, 75, 115, 2, 44, 65, 1, 12, 19,
+ 55, 153, 210, 24, 130, 194, 3, 93, 146, 1, 61, 97, 1, 31, 50, 1, 10, 16,
+ 49, 186, 223, 17, 148, 204, 1, 96, 142, 1, 53, 83, 1, 26, 44, 1, 11, 17,
+ 13, 217, 212, 2, 136, 180, 1, 78, 124, 1, 50, 83, 1, 29, 49, 1, 14, 23,
+ 197, 13, 247, 82, 17, 222, 25, 17, 162, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 126, 186, 247, 234, 191, 243, 176, 177, 234, 104, 158, 220, 66, 128, 186, 55, 90, 137,
+ 111, 197, 242, 46, 158, 219, 9, 104, 171, 2, 65, 125, 1, 44, 80, 1, 17, 91,
+ 104, 208, 245, 39, 168, 224, 3, 109, 162, 1, 79, 124, 1, 50, 102, 1, 43, 102,
+ 84, 220, 246, 31, 177, 231, 2, 115, 180, 1, 79, 134, 1, 55, 77, 1, 60, 79,
+ 43, 243, 240, 8, 180, 217, 1, 115, 166, 1, 84, 121, 1, 51, 67, 1, 16, 6,
+ },
+ .switchable_interp_prob{235, 162, 36, 255, 34, 3, 149, 144},
+ .inter_mode_prob{
+ 2, 173, 34, 0, 7, 145, 85, 0, 7, 166, 63, 0, 7, 94,
+ 66, 0, 8, 64, 46, 0, 17, 81, 31, 0, 25, 29, 30, 0,
+ },
+ .intra_inter_prob{9, 102, 187, 225},
+ .comp_inter_prob{9, 102, 187, 225, 0},
+ .single_ref_prob{33, 16, 77, 74, 142, 142, 172, 170, 238, 247},
+ .comp_ref_prob{50, 126, 123, 221, 226},
+ .tx_32x32_prob{3, 136, 37, 5, 52, 13},
+ .tx_16x16_prob{20, 152, 15, 101},
+ .tx_8x8_prob{100, 66},
+ .skip_probs{192, 128, 64},
+ .joints{32, 64, 96},
+ .sign{128, 128},
+ .classes{
+ 224, 144, 192, 168, 192, 176, 192, 198, 198, 245,
+ 216, 128, 176, 160, 176, 176, 192, 198, 198, 208,
+ },
+ .class_0{216, 208},
+ .prob_bits{
+ 136, 140, 148, 160, 176, 192, 224, 234, 234, 240,
+ 136, 140, 148, 160, 176, 192, 224, 234, 234, 240,
+ },
+ .class_0_fr{128, 128, 64, 96, 112, 64, 128, 128, 64, 96, 112, 64},
+ .fr{64, 96, 64, 64, 96, 64},
+ .class_0_hp{160, 160},
+ .high_precision{128, 128},
+};
+
+constexpr std::array<u8, 256> norm_lut{
+ 0, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+constexpr std::array<u8, 254> map_lut{
+ 20, 21, 22, 23, 24, 25, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
+ 1, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 2, 50, 51, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, 3, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
+ 73, 4, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 5, 86, 87, 88, 89,
+ 90, 91, 92, 93, 94, 95, 96, 97, 6, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
+ 108, 109, 7, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 8, 122, 123, 124,
+ 125, 126, 127, 128, 129, 130, 131, 132, 133, 9, 134, 135, 136, 137, 138, 139, 140, 141, 142,
+ 143, 144, 145, 10, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 11, 158, 159,
+ 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 12, 170, 171, 172, 173, 174, 175, 176, 177,
+ 178, 179, 180, 181, 13, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 14, 194,
+ 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 15, 206, 207, 208, 209, 210, 211, 212,
+ 213, 214, 215, 216, 217, 16, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 17,
+ 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 18, 242, 243, 244, 245, 246, 247,
+ 248, 249, 250, 251, 252, 253, 19,
+};
+
+// 6.2.14 Tile size calculation
+
+[[nodiscard]] s32 CalcMinLog2TileCols(s32 frame_width) {
+ const s32 sb64_cols = (frame_width + 63) / 64;
+ s32 min_log2 = 0;
+
+ while ((64 << min_log2) < sb64_cols) {
+ min_log2++;
+ }
+
+ return min_log2;
+}
+
+[[nodiscard]] s32 CalcMaxLog2TileCols(s32 frame_width) {
+ const s32 sb64_cols = (frame_width + 63) / 64;
+ s32 max_log2 = 1;
+
+ while ((sb64_cols >> max_log2) >= 4) {
+ max_log2++;
+ }
+
+ return max_log2 - 1;
+}
+
+// Recenters probability. Based on section 6.3.6 of VP9 Specification
+[[nodiscard]] s32 RecenterNonNeg(s32 new_prob, s32 old_prob) {
+ if (new_prob > old_prob * 2) {
+ return new_prob;
+ }
+
+ if (new_prob >= old_prob) {
+ return (new_prob - old_prob) * 2;
+ }
+
+ return (old_prob - new_prob) * 2 - 1;
+}
+
+// Adjusts old_prob depending on new_prob. Based on section 6.3.5 of VP9 Specification
+[[nodiscard]] s32 RemapProbability(s32 new_prob, s32 old_prob) {
+ new_prob--;
+ old_prob--;
+
+ std::size_t index{};
+
+ if (old_prob * 2 <= 0xff) {
+ index = static_cast<std::size_t>(std::max(0, RecenterNonNeg(new_prob, old_prob) - 1));
+ } else {
+ index = static_cast<std::size_t>(
+ std::max(0, RecenterNonNeg(0xff - 1 - new_prob, 0xff - 1 - old_prob) - 1));
+ }
+
+ return static_cast<s32>(map_lut[index]);
+}
+} // Anonymous namespace
+
+VP9::VP9(Host1x::Host1x& host1x_) : host1x{host1x_} {}
+
+VP9::~VP9() = default;
+
+void VP9::WriteProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) {
+ const bool update = new_prob != old_prob;
+
+ writer.Write(update, diff_update_probability);
+
+ if (update) {
+ WriteProbabilityDelta(writer, new_prob, old_prob);
+ }
+}
+template <typename T, std::size_t N>
+void VP9::WriteProbabilityUpdate(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
+ const std::array<T, N>& old_prob) {
+ for (std::size_t offset = 0; offset < new_prob.size(); ++offset) {
+ WriteProbabilityUpdate(writer, new_prob[offset], old_prob[offset]);
+ }
+}
+
+template <typename T, std::size_t N>
+void VP9::WriteProbabilityUpdateAligned4(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
+ const std::array<T, N>& old_prob) {
+ for (std::size_t offset = 0; offset < new_prob.size(); offset += 4) {
+ WriteProbabilityUpdate(writer, new_prob[offset + 0], old_prob[offset + 0]);
+ WriteProbabilityUpdate(writer, new_prob[offset + 1], old_prob[offset + 1]);
+ WriteProbabilityUpdate(writer, new_prob[offset + 2], old_prob[offset + 2]);
+ }
+}
+
+void VP9::WriteProbabilityDelta(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) {
+ const int delta = RemapProbability(new_prob, old_prob);
+
+ EncodeTermSubExp(writer, delta);
+}
+
+void VP9::EncodeTermSubExp(VpxRangeEncoder& writer, s32 value) {
+ if (WriteLessThan(writer, value, 16)) {
+ writer.Write(value, 4);
+ } else if (WriteLessThan(writer, value, 32)) {
+ writer.Write(value - 16, 4);
+ } else if (WriteLessThan(writer, value, 64)) {
+ writer.Write(value - 32, 5);
+ } else {
+ value -= 64;
+
+ constexpr s32 size = 8;
+
+ const s32 mask = (1 << size) - 191;
+
+ const s32 delta = value - mask;
+
+ if (delta < 0) {
+ writer.Write(value, size - 1);
+ } else {
+ writer.Write(delta / 2 + mask, size - 1);
+ writer.Write(delta & 1, 1);
+ }
+ }
+}
+
+bool VP9::WriteLessThan(VpxRangeEncoder& writer, s32 value, s32 test) {
+ const bool is_lt = value < test;
+ writer.Write(!is_lt);
+ return is_lt;
+}
+
+void VP9::WriteCoefProbabilityUpdate(VpxRangeEncoder& writer, s32 tx_mode,
+ const std::array<u8, 1728>& new_prob,
+ const std::array<u8, 1728>& old_prob) {
+ constexpr u32 block_bytes = 2 * 2 * 6 * 6 * 3;
+
+ const auto needs_update = [&](u32 base_index) {
+ return !std::equal(new_prob.begin() + base_index,
+ new_prob.begin() + base_index + block_bytes,
+ old_prob.begin() + base_index);
+ };
+
+ for (u32 block_index = 0; block_index < 4; block_index++) {
+ const u32 base_index = block_index * block_bytes;
+ const bool update = needs_update(base_index);
+ writer.Write(update);
+
+ if (update) {
+ u32 index = base_index;
+ for (s32 i = 0; i < 2; i++) {
+ for (s32 j = 0; j < 2; j++) {
+ for (s32 k = 0; k < 6; k++) {
+ for (s32 l = 0; l < 6; l++) {
+ if (k != 0 || l < 3) {
+ WriteProbabilityUpdate(writer, new_prob[index + 0],
+ old_prob[index + 0]);
+ WriteProbabilityUpdate(writer, new_prob[index + 1],
+ old_prob[index + 1]);
+ WriteProbabilityUpdate(writer, new_prob[index + 2],
+ old_prob[index + 2]);
+ }
+ index += 3;
+ }
+ }
+ }
+ }
+ }
+ if (block_index == static_cast<u32>(tx_mode)) {
+ break;
+ }
+ }
+}
+
+void VP9::WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) {
+ const bool update = new_prob != old_prob;
+ writer.Write(update, diff_update_probability);
+
+ if (update) {
+ writer.Write(new_prob >> 1, 7);
+ }
+}
+
+Vp9PictureInfo VP9::GetVp9PictureInfo(const Host1x::NvdecCommon::NvdecRegisters& state) {
+ PictureInfo picture_info;
+ host1x.MemoryManager().ReadBlock(state.picture_info_offset, &picture_info, sizeof(PictureInfo));
+ Vp9PictureInfo vp9_info = picture_info.Convert();
+
+ InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy);
+
+ // surface_luma_offset[0:3] contains the address of the reference frame offsets in the following
+ // order: last, golden, altref, current.
+ std::copy(state.surface_luma_offset.begin(), state.surface_luma_offset.begin() + 4,
+ vp9_info.frame_offsets.begin());
+
+ return vp9_info;
+}
+
+void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
+ EntropyProbs entropy;
+ host1x.MemoryManager().ReadBlock(offset, &entropy, sizeof(EntropyProbs));
+ entropy.Convert(dst);
+}
+
+Vp9FrameContainer VP9::GetCurrentFrame(const Host1x::NvdecCommon::NvdecRegisters& state) {
+ Vp9FrameContainer current_frame{};
+ {
+ // gpu.SyncGuestHost(); epic, why?
+ current_frame.info = GetVp9PictureInfo(state);
+ current_frame.bit_stream.resize(current_frame.info.bitstream_size);
+ host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset,
+ current_frame.bit_stream.data(),
+ current_frame.info.bitstream_size);
+ }
+ if (!next_frame.bit_stream.empty()) {
+ Vp9FrameContainer temp{
+ .info = current_frame.info,
+ .bit_stream = std::move(current_frame.bit_stream),
+ };
+ next_frame.info.show_frame = current_frame.info.last_frame_shown;
+ current_frame.info = next_frame.info;
+ current_frame.bit_stream = std::move(next_frame.bit_stream);
+ next_frame = std::move(temp);
+ } else {
+ next_frame.info = current_frame.info;
+ next_frame.bit_stream = current_frame.bit_stream;
+ }
+ return current_frame;
+}
+
+std::vector<u8> VP9::ComposeCompressedHeader() {
+ VpxRangeEncoder writer{};
+ const bool update_probs = !current_frame_info.is_key_frame && current_frame_info.show_frame;
+ if (!current_frame_info.lossless) {
+ if (static_cast<u32>(current_frame_info.transform_mode) >= 3) {
+ writer.Write(3, 2);
+ writer.Write(current_frame_info.transform_mode == 4);
+ } else {
+ writer.Write(current_frame_info.transform_mode, 2);
+ }
+ }
+
+ if (current_frame_info.transform_mode == 4) {
+ // tx_mode_probs() in the spec
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_8x8_prob,
+ prev_frame_probs.tx_8x8_prob);
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_16x16_prob,
+ prev_frame_probs.tx_16x16_prob);
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_32x32_prob,
+ prev_frame_probs.tx_32x32_prob);
+ if (update_probs) {
+ prev_frame_probs.tx_8x8_prob = current_frame_info.entropy.tx_8x8_prob;
+ prev_frame_probs.tx_16x16_prob = current_frame_info.entropy.tx_16x16_prob;
+ prev_frame_probs.tx_32x32_prob = current_frame_info.entropy.tx_32x32_prob;
+ }
+ }
+ // read_coef_probs() in the spec
+ WriteCoefProbabilityUpdate(writer, current_frame_info.transform_mode,
+ current_frame_info.entropy.coef_probs, prev_frame_probs.coef_probs);
+ // read_skip_probs() in the spec
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.skip_probs,
+ prev_frame_probs.skip_probs);
+
+ if (update_probs) {
+ prev_frame_probs.coef_probs = current_frame_info.entropy.coef_probs;
+ prev_frame_probs.skip_probs = current_frame_info.entropy.skip_probs;
+ }
+
+ if (!current_frame_info.intra_only) {
+ // read_inter_probs() in the spec
+ WriteProbabilityUpdateAligned4(writer, current_frame_info.entropy.inter_mode_prob,
+ prev_frame_probs.inter_mode_prob);
+
+ if (current_frame_info.interp_filter == 4) {
+ // read_interp_filter_probs() in the spec
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.switchable_interp_prob,
+ prev_frame_probs.switchable_interp_prob);
+ if (update_probs) {
+ prev_frame_probs.switchable_interp_prob =
+ current_frame_info.entropy.switchable_interp_prob;
+ }
+ }
+
+ // read_is_inter_probs() in the spec
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.intra_inter_prob,
+ prev_frame_probs.intra_inter_prob);
+
+ // frame_reference_mode() in the spec
+ if ((current_frame_info.ref_frame_sign_bias[1] & 1) !=
+ (current_frame_info.ref_frame_sign_bias[2] & 1) ||
+ (current_frame_info.ref_frame_sign_bias[1] & 1) !=
+ (current_frame_info.ref_frame_sign_bias[3] & 1)) {
+ if (current_frame_info.reference_mode >= 1) {
+ writer.Write(1, 1);
+ writer.Write(current_frame_info.reference_mode == 2);
+ } else {
+ writer.Write(0, 1);
+ }
+ }
+
+ // frame_reference_mode_probs() in the spec
+ if (current_frame_info.reference_mode == 2) {
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.comp_inter_prob,
+ prev_frame_probs.comp_inter_prob);
+ if (update_probs) {
+ prev_frame_probs.comp_inter_prob = current_frame_info.entropy.comp_inter_prob;
+ }
+ }
+
+ if (current_frame_info.reference_mode != 1) {
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.single_ref_prob,
+ prev_frame_probs.single_ref_prob);
+ if (update_probs) {
+ prev_frame_probs.single_ref_prob = current_frame_info.entropy.single_ref_prob;
+ }
+ }
+
+ if (current_frame_info.reference_mode != 0) {
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.comp_ref_prob,
+ prev_frame_probs.comp_ref_prob);
+ if (update_probs) {
+ prev_frame_probs.comp_ref_prob = current_frame_info.entropy.comp_ref_prob;
+ }
+ }
+
+ // read_y_mode_probs
+ for (std::size_t index = 0; index < current_frame_info.entropy.y_mode_prob.size();
+ ++index) {
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.y_mode_prob[index],
+ prev_frame_probs.y_mode_prob[index]);
+ }
+
+ // read_partition_probs
+ WriteProbabilityUpdateAligned4(writer, current_frame_info.entropy.partition_prob,
+ prev_frame_probs.partition_prob);
+
+ // mv_probs
+ for (s32 i = 0; i < 3; i++) {
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.joints[i],
+ prev_frame_probs.joints[i]);
+ }
+ if (update_probs) {
+ prev_frame_probs.inter_mode_prob = current_frame_info.entropy.inter_mode_prob;
+ prev_frame_probs.intra_inter_prob = current_frame_info.entropy.intra_inter_prob;
+ prev_frame_probs.y_mode_prob = current_frame_info.entropy.y_mode_prob;
+ prev_frame_probs.partition_prob = current_frame_info.entropy.partition_prob;
+ prev_frame_probs.joints = current_frame_info.entropy.joints;
+ }
+
+ for (s32 i = 0; i < 2; i++) {
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.sign[i],
+ prev_frame_probs.sign[i]);
+ for (s32 j = 0; j < 10; j++) {
+ const int index = i * 10 + j;
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.classes[index],
+ prev_frame_probs.classes[index]);
+ }
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0[i],
+ prev_frame_probs.class_0[i]);
+
+ for (s32 j = 0; j < 10; j++) {
+ const int index = i * 10 + j;
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.prob_bits[index],
+ prev_frame_probs.prob_bits[index]);
+ }
+ }
+
+ for (s32 i = 0; i < 2; i++) {
+ for (s32 j = 0; j < 2; j++) {
+ for (s32 k = 0; k < 3; k++) {
+ const int index = i * 2 * 3 + j * 3 + k;
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0_fr[index],
+ prev_frame_probs.class_0_fr[index]);
+ }
+ }
+
+ for (s32 j = 0; j < 3; j++) {
+ const int index = i * 3 + j;
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.fr[index],
+ prev_frame_probs.fr[index]);
+ }
+ }
+
+ if (current_frame_info.allow_high_precision_mv) {
+ for (s32 index = 0; index < 2; index++) {
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0_hp[index],
+ prev_frame_probs.class_0_hp[index]);
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.high_precision[index],
+ prev_frame_probs.high_precision[index]);
+ }
+ }
+
+ // save previous probs
+ if (update_probs) {
+ prev_frame_probs.sign = current_frame_info.entropy.sign;
+ prev_frame_probs.classes = current_frame_info.entropy.classes;
+ prev_frame_probs.class_0 = current_frame_info.entropy.class_0;
+ prev_frame_probs.prob_bits = current_frame_info.entropy.prob_bits;
+ prev_frame_probs.class_0_fr = current_frame_info.entropy.class_0_fr;
+ prev_frame_probs.fr = current_frame_info.entropy.fr;
+ prev_frame_probs.class_0_hp = current_frame_info.entropy.class_0_hp;
+ prev_frame_probs.high_precision = current_frame_info.entropy.high_precision;
+ }
+ }
+ writer.End();
+ return writer.GetBuffer();
+}
+
+VpxBitStreamWriter VP9::ComposeUncompressedHeader() {
+ VpxBitStreamWriter uncomp_writer{};
+
+ uncomp_writer.WriteU(2, 2); // Frame marker.
+ uncomp_writer.WriteU(0, 2); // Profile.
+ uncomp_writer.WriteBit(false); // Show existing frame.
+ uncomp_writer.WriteBit(!current_frame_info.is_key_frame); // is key frame?
+ uncomp_writer.WriteBit(current_frame_info.show_frame); // show frame?
+ uncomp_writer.WriteBit(current_frame_info.error_resilient_mode); // error reslience
+
+ if (current_frame_info.is_key_frame) {
+ uncomp_writer.WriteU(frame_sync_code, 24);
+ uncomp_writer.WriteU(0, 3); // Color space.
+ uncomp_writer.WriteU(0, 1); // Color range.
+ uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16);
+ uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16);
+ uncomp_writer.WriteBit(false); // Render and frame size different.
+
+ // Reset context
+ prev_frame_probs = default_probs;
+ swap_ref_indices = false;
+ loop_filter_ref_deltas.fill(0);
+ loop_filter_mode_deltas.fill(0);
+ frame_ctxs.fill(default_probs);
+
+ // intra only, meaning the frame can be recreated with no other references
+ current_frame_info.intra_only = true;
+ } else {
+ if (!current_frame_info.show_frame) {
+ uncomp_writer.WriteBit(current_frame_info.intra_only);
+ } else {
+ current_frame_info.intra_only = false;
+ }
+ if (!current_frame_info.error_resilient_mode) {
+ uncomp_writer.WriteU(0, 2); // Reset frame context.
+ }
+ const auto& curr_offsets = current_frame_info.frame_offsets;
+ const auto& next_offsets = next_frame.info.frame_offsets;
+ const bool ref_frames_different = curr_offsets[1] != curr_offsets[2];
+ const bool next_references_swap =
+ (next_offsets[1] == curr_offsets[2]) || (next_offsets[2] == curr_offsets[1]);
+ const bool needs_ref_swap = ref_frames_different && next_references_swap;
+ if (needs_ref_swap) {
+ swap_ref_indices = !swap_ref_indices;
+ }
+ union {
+ u32 raw;
+ BitField<0, 1, u32> refresh_last;
+ BitField<1, 2, u32> refresh_golden;
+ BitField<2, 1, u32> refresh_alt;
+ } refresh_frame_flags;
+
+ refresh_frame_flags.raw = 0;
+ for (u32 index = 0; index < 3; ++index) {
+ // Refresh indices that use the current frame as an index
+ if (curr_offsets[3] == next_offsets[index]) {
+ refresh_frame_flags.raw |= 1u << index;
+ }
+ }
+ if (swap_ref_indices) {
+ const u32 temp = refresh_frame_flags.refresh_golden;
+ refresh_frame_flags.refresh_golden.Assign(refresh_frame_flags.refresh_alt.Value());
+ refresh_frame_flags.refresh_alt.Assign(temp);
+ }
+ if (current_frame_info.intra_only) {
+ uncomp_writer.WriteU(frame_sync_code, 24);
+ uncomp_writer.WriteU(refresh_frame_flags.raw, 8);
+ uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16);
+ uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16);
+ uncomp_writer.WriteBit(false); // Render and frame size different.
+ } else {
+ const bool swap_indices = needs_ref_swap ^ swap_ref_indices;
+ const auto ref_frame_index = swap_indices ? std::array{0, 2, 1} : std::array{0, 1, 2};
+ uncomp_writer.WriteU(refresh_frame_flags.raw, 8);
+ for (size_t index = 1; index < 4; index++) {
+ uncomp_writer.WriteU(ref_frame_index[index - 1], 3);
+ uncomp_writer.WriteU(current_frame_info.ref_frame_sign_bias[index], 1);
+ }
+ uncomp_writer.WriteBit(true); // Frame size with refs.
+ uncomp_writer.WriteBit(false); // Render and frame size different.
+ uncomp_writer.WriteBit(current_frame_info.allow_high_precision_mv);
+ uncomp_writer.WriteBit(current_frame_info.interp_filter == 4);
+
+ if (current_frame_info.interp_filter != 4) {
+ uncomp_writer.WriteU(current_frame_info.interp_filter, 2);
+ }
+ }
+ }
+
+ if (!current_frame_info.error_resilient_mode) {
+ uncomp_writer.WriteBit(true); // Refresh frame context. where do i get this info from?
+ uncomp_writer.WriteBit(true); // Frame parallel decoding mode.
+ }
+
+ int frame_ctx_idx = 0;
+ if (!current_frame_info.show_frame) {
+ frame_ctx_idx = 1;
+ }
+
+ uncomp_writer.WriteU(frame_ctx_idx, 2); // Frame context index.
+ prev_frame_probs = frame_ctxs[frame_ctx_idx]; // reference probabilities for compressed header
+ frame_ctxs[frame_ctx_idx] = current_frame_info.entropy;
+
+ uncomp_writer.WriteU(current_frame_info.first_level, 6);
+ uncomp_writer.WriteU(current_frame_info.sharpness_level, 3);
+ uncomp_writer.WriteBit(current_frame_info.mode_ref_delta_enabled);
+
+ if (current_frame_info.mode_ref_delta_enabled) {
+ // check if ref deltas are different, update accordingly
+ std::array<bool, 4> update_loop_filter_ref_deltas;
+ std::array<bool, 2> update_loop_filter_mode_deltas;
+
+ bool loop_filter_delta_update = false;
+
+ for (std::size_t index = 0; index < current_frame_info.ref_deltas.size(); index++) {
+ const s8 old_deltas = loop_filter_ref_deltas[index];
+ const s8 new_deltas = current_frame_info.ref_deltas[index];
+ const bool differing_delta = old_deltas != new_deltas;
+
+ update_loop_filter_ref_deltas[index] = differing_delta;
+ loop_filter_delta_update |= differing_delta;
+ }
+
+ for (std::size_t index = 0; index < current_frame_info.mode_deltas.size(); index++) {
+ const s8 old_deltas = loop_filter_mode_deltas[index];
+ const s8 new_deltas = current_frame_info.mode_deltas[index];
+ const bool differing_delta = old_deltas != new_deltas;
+
+ update_loop_filter_mode_deltas[index] = differing_delta;
+ loop_filter_delta_update |= differing_delta;
+ }
+
+ uncomp_writer.WriteBit(loop_filter_delta_update);
+
+ if (loop_filter_delta_update) {
+ for (std::size_t index = 0; index < current_frame_info.ref_deltas.size(); index++) {
+ uncomp_writer.WriteBit(update_loop_filter_ref_deltas[index]);
+
+ if (update_loop_filter_ref_deltas[index]) {
+ uncomp_writer.WriteS(current_frame_info.ref_deltas[index], 6);
+ }
+ }
+
+ for (std::size_t index = 0; index < current_frame_info.mode_deltas.size(); index++) {
+ uncomp_writer.WriteBit(update_loop_filter_mode_deltas[index]);
+
+ if (update_loop_filter_mode_deltas[index]) {
+ uncomp_writer.WriteS(current_frame_info.mode_deltas[index], 6);
+ }
+ }
+ // save new deltas
+ loop_filter_ref_deltas = current_frame_info.ref_deltas;
+ loop_filter_mode_deltas = current_frame_info.mode_deltas;
+ }
+ }
+
+ uncomp_writer.WriteU(current_frame_info.base_q_index, 8);
+
+ uncomp_writer.WriteDeltaQ(current_frame_info.y_dc_delta_q);
+ uncomp_writer.WriteDeltaQ(current_frame_info.uv_dc_delta_q);
+ uncomp_writer.WriteDeltaQ(current_frame_info.uv_ac_delta_q);
+
+ ASSERT(!current_frame_info.segment_enabled);
+ uncomp_writer.WriteBit(false); // Segmentation enabled (TODO).
+
+ const s32 min_tile_cols_log2 = CalcMinLog2TileCols(current_frame_info.frame_size.width);
+ const s32 max_tile_cols_log2 = CalcMaxLog2TileCols(current_frame_info.frame_size.width);
+
+ const s32 tile_cols_log2_diff = current_frame_info.log2_tile_cols - min_tile_cols_log2;
+ const s32 tile_cols_log2_inc_mask = (1 << tile_cols_log2_diff) - 1;
+
+ // If it's less than the maximum, we need to add an extra 0 on the bitstream
+ // to indicate that it should stop reading.
+ if (current_frame_info.log2_tile_cols < max_tile_cols_log2) {
+ uncomp_writer.WriteU(tile_cols_log2_inc_mask << 1, tile_cols_log2_diff + 1);
+ } else {
+ uncomp_writer.WriteU(tile_cols_log2_inc_mask, tile_cols_log2_diff);
+ }
+
+ const bool tile_rows_log2_is_nonzero = current_frame_info.log2_tile_rows != 0;
+
+ uncomp_writer.WriteBit(tile_rows_log2_is_nonzero);
+
+ if (tile_rows_log2_is_nonzero) {
+ uncomp_writer.WriteBit(current_frame_info.log2_tile_rows > 1);
+ }
+
+ return uncomp_writer;
+}
+
+void VP9::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state) {
+ std::vector<u8> bitstream;
+ {
+ Vp9FrameContainer curr_frame = GetCurrentFrame(state);
+ current_frame_info = curr_frame.info;
+ bitstream = std::move(curr_frame.bit_stream);
+ }
+ // The uncompressed header routine sets PrevProb parameters needed for the compressed header
+ auto uncomp_writer = ComposeUncompressedHeader();
+ std::vector<u8> compressed_header = ComposeCompressedHeader();
+
+ uncomp_writer.WriteU(static_cast<s32>(compressed_header.size()), 16);
+ uncomp_writer.Flush();
+ std::vector<u8> uncompressed_header = uncomp_writer.GetByteArray();
+
+ // Write headers and frame to buffer
+ frame.resize(uncompressed_header.size() + compressed_header.size() + bitstream.size());
+ std::copy(uncompressed_header.begin(), uncompressed_header.end(), frame.begin());
+ std::copy(compressed_header.begin(), compressed_header.end(),
+ frame.begin() + uncompressed_header.size());
+ std::copy(bitstream.begin(), bitstream.end(),
+ frame.begin() + uncompressed_header.size() + compressed_header.size());
+}
+
+VpxRangeEncoder::VpxRangeEncoder() {
+ Write(false);
+}
+
+VpxRangeEncoder::~VpxRangeEncoder() = default;
+
+void VpxRangeEncoder::Write(s32 value, s32 value_size) {
+ for (s32 bit = value_size - 1; bit >= 0; bit--) {
+ Write(((value >> bit) & 1) != 0);
+ }
+}
+
+void VpxRangeEncoder::Write(bool bit) {
+ Write(bit, half_probability);
+}
+
+void VpxRangeEncoder::Write(bool bit, s32 probability) {
+ u32 local_range = range;
+ const u32 split = 1 + (((local_range - 1) * static_cast<u32>(probability)) >> 8);
+ local_range = split;
+
+ if (bit) {
+ low_value += split;
+ local_range = range - split;
+ }
+
+ s32 shift = static_cast<s32>(norm_lut[local_range]);
+ local_range <<= shift;
+ count += shift;
+
+ if (count >= 0) {
+ const s32 offset = shift - count;
+
+ if (((low_value << (offset - 1)) >> 31) != 0) {
+ const s32 current_pos = static_cast<s32>(base_stream.GetPosition());
+ base_stream.Seek(-1, Common::SeekOrigin::FromCurrentPos);
+ while (PeekByte() == 0xff) {
+ base_stream.WriteByte(0);
+
+ base_stream.Seek(-2, Common::SeekOrigin::FromCurrentPos);
+ }
+ base_stream.WriteByte(static_cast<u8>((PeekByte() + 1)));
+ base_stream.Seek(current_pos, Common::SeekOrigin::SetOrigin);
+ }
+ base_stream.WriteByte(static_cast<u8>((low_value >> (24 - offset))));
+
+ low_value <<= offset;
+ shift = count;
+ low_value &= 0xffffff;
+ count -= 8;
+ }
+
+ low_value <<= shift;
+ range = local_range;
+}
+
+void VpxRangeEncoder::End() {
+ for (std::size_t index = 0; index < 32; ++index) {
+ Write(false);
+ }
+}
+
+u8 VpxRangeEncoder::PeekByte() {
+ const u8 value = base_stream.ReadByte();
+ base_stream.Seek(-1, Common::SeekOrigin::FromCurrentPos);
+
+ return value;
+}
+
+VpxBitStreamWriter::VpxBitStreamWriter() = default;
+
+VpxBitStreamWriter::~VpxBitStreamWriter() = default;
+
+void VpxBitStreamWriter::WriteU(u32 value, u32 value_size) {
+ WriteBits(value, value_size);
+}
+
+void VpxBitStreamWriter::WriteS(s32 value, u32 value_size) {
+ const bool sign = value < 0;
+ if (sign) {
+ value = -value;
+ }
+
+ WriteBits(static_cast<u32>(value << 1) | (sign ? 1 : 0), value_size + 1);
+}
+
+void VpxBitStreamWriter::WriteDeltaQ(u32 value) {
+ const bool delta_coded = value != 0;
+ WriteBit(delta_coded);
+
+ if (delta_coded) {
+ WriteBits(value, 4);
+ }
+}
+
+void VpxBitStreamWriter::WriteBits(u32 value, u32 bit_count) {
+ s32 value_pos = 0;
+ s32 remaining = bit_count;
+
+ while (remaining > 0) {
+ s32 copy_size = remaining;
+
+ const s32 free = GetFreeBufferBits();
+
+ if (copy_size > free) {
+ copy_size = free;
+ }
+
+ const s32 mask = (1 << copy_size) - 1;
+
+ const s32 src_shift = (bit_count - value_pos) - copy_size;
+ const s32 dst_shift = (buffer_size - buffer_pos) - copy_size;
+
+ buffer |= ((value >> src_shift) & mask) << dst_shift;
+
+ value_pos += copy_size;
+ buffer_pos += copy_size;
+ remaining -= copy_size;
+ }
+}
+
+void VpxBitStreamWriter::WriteBit(bool state) {
+ WriteBits(state ? 1 : 0, 1);
+}
+
+s32 VpxBitStreamWriter::GetFreeBufferBits() {
+ if (buffer_pos == buffer_size) {
+ Flush();
+ }
+
+ return buffer_size - buffer_pos;
+}
+
+void VpxBitStreamWriter::Flush() {
+ if (buffer_pos == 0) {
+ return;
+ }
+ byte_array.push_back(static_cast<u8>(buffer));
+ buffer = 0;
+ buffer_pos = 0;
+}
+
+std::vector<u8>& VpxBitStreamWriter::GetByteArray() {
+ return byte_array;
+}
+
+const std::vector<u8>& VpxBitStreamWriter::GetByteArray() const {
+ return byte_array;
+}
+
+} // namespace Tegra::Decoder
diff --git a/src/video_core/host1x/codecs/vp9.h b/src/video_core/host1x/codecs/vp9.h
new file mode 100644
index 000000000..d4083e8d3
--- /dev/null
+++ b/src/video_core/host1x/codecs/vp9.h
@@ -0,0 +1,198 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_types.h"
+#include "common/stream.h"
+#include "video_core/host1x/codecs/vp9_types.h"
+#include "video_core/host1x/nvdec_common.h"
+
+namespace Tegra {
+
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
+
+namespace Decoder {
+
+/// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the
+/// VP9 header bitstreams.
+
+class VpxRangeEncoder {
+public:
+ VpxRangeEncoder();
+ ~VpxRangeEncoder();
+
+ VpxRangeEncoder(const VpxRangeEncoder&) = delete;
+ VpxRangeEncoder& operator=(const VpxRangeEncoder&) = delete;
+
+ VpxRangeEncoder(VpxRangeEncoder&&) = default;
+ VpxRangeEncoder& operator=(VpxRangeEncoder&&) = default;
+
+ /// Writes the rightmost value_size bits from value into the stream
+ void Write(s32 value, s32 value_size);
+
+ /// Writes a single bit with half probability
+ void Write(bool bit);
+
+ /// Writes a bit to the base_stream encoded with probability
+ void Write(bool bit, s32 probability);
+
+ /// Signal the end of the bitstream
+ void End();
+
+ [[nodiscard]] std::vector<u8>& GetBuffer() {
+ return base_stream.GetBuffer();
+ }
+
+ [[nodiscard]] const std::vector<u8>& GetBuffer() const {
+ return base_stream.GetBuffer();
+ }
+
+private:
+ u8 PeekByte();
+ Common::Stream base_stream{};
+ u32 low_value{};
+ u32 range{0xff};
+ s32 count{-24};
+ s32 half_probability{128};
+};
+
+class VpxBitStreamWriter {
+public:
+ VpxBitStreamWriter();
+ ~VpxBitStreamWriter();
+
+ VpxBitStreamWriter(const VpxBitStreamWriter&) = delete;
+ VpxBitStreamWriter& operator=(const VpxBitStreamWriter&) = delete;
+
+ VpxBitStreamWriter(VpxBitStreamWriter&&) = default;
+ VpxBitStreamWriter& operator=(VpxBitStreamWriter&&) = default;
+
+ /// Write an unsigned integer value
+ void WriteU(u32 value, u32 value_size);
+
+ /// Write a signed integer value
+ void WriteS(s32 value, u32 value_size);
+
+ /// Based on 6.2.10 of VP9 Spec, writes a delta coded value
+ void WriteDeltaQ(u32 value);
+
+ /// Write a single bit.
+ void WriteBit(bool state);
+
+ /// Pushes current buffer into buffer_array, resets buffer
+ void Flush();
+
+ /// Returns byte_array
+ [[nodiscard]] std::vector<u8>& GetByteArray();
+
+ /// Returns const byte_array
+ [[nodiscard]] const std::vector<u8>& GetByteArray() const;
+
+private:
+ /// Write bit_count bits from value into buffer
+ void WriteBits(u32 value, u32 bit_count);
+
+ /// Gets next available position in buffer, invokes Flush() if buffer is full
+ s32 GetFreeBufferBits();
+
+ s32 buffer_size{8};
+
+ s32 buffer{};
+ s32 buffer_pos{};
+ std::vector<u8> byte_array;
+};
+
+class VP9 {
+public:
+ explicit VP9(Host1x::Host1x& host1x);
+ ~VP9();
+
+ VP9(const VP9&) = delete;
+ VP9& operator=(const VP9&) = delete;
+
+ VP9(VP9&&) = default;
+ VP9& operator=(VP9&&) = delete;
+
+ /// Composes the VP9 frame from the GPU state information.
+ /// Based on the official VP9 spec documentation
+ void ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state);
+
+ /// Returns true if the most recent frame was a hidden frame.
+ [[nodiscard]] bool WasFrameHidden() const {
+ return !current_frame_info.show_frame;
+ }
+
+ /// Returns a const reference to the composed frame data.
+ [[nodiscard]] const std::vector<u8>& GetFrameBytes() const {
+ return frame;
+ }
+
+private:
+ /// Generates compressed header probability updates in the bitstream writer
+ template <typename T, std::size_t N>
+ void WriteProbabilityUpdate(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
+ const std::array<T, N>& old_prob);
+
+ /// Generates compressed header probability updates in the bitstream writer
+ /// If probs are not equal, WriteProbabilityDelta is invoked
+ void WriteProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
+
+ /// Generates compressed header probability deltas in the bitstream writer
+ void WriteProbabilityDelta(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
+
+ /// Inverse of 6.3.4 Decode term subexp
+ void EncodeTermSubExp(VpxRangeEncoder& writer, s32 value);
+
+ /// Writes if the value is less than the test value
+ bool WriteLessThan(VpxRangeEncoder& writer, s32 value, s32 test);
+
+ /// Writes probability updates for the Coef probabilities
+ void WriteCoefProbabilityUpdate(VpxRangeEncoder& writer, s32 tx_mode,
+ const std::array<u8, 1728>& new_prob,
+ const std::array<u8, 1728>& old_prob);
+
+ /// Write probabilities for 4-byte aligned structures
+ template <typename T, std::size_t N>
+ void WriteProbabilityUpdateAligned4(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
+ const std::array<T, N>& old_prob);
+
+ /// Write motion vector probability updates. 6.3.17 in the spec
+ void WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
+
+ /// Returns VP9 information from NVDEC provided offset and size
+ [[nodiscard]] Vp9PictureInfo GetVp9PictureInfo(
+ const Host1x::NvdecCommon::NvdecRegisters& state);
+
+ /// Read and convert NVDEC provided entropy probs to Vp9EntropyProbs struct
+ void InsertEntropy(u64 offset, Vp9EntropyProbs& dst);
+
+ /// Returns frame to be decoded after buffering
+ [[nodiscard]] Vp9FrameContainer GetCurrentFrame(
+ const Host1x::NvdecCommon::NvdecRegisters& state);
+
+ /// Use NVDEC providied information to compose the headers for the current frame
+ [[nodiscard]] std::vector<u8> ComposeCompressedHeader();
+ [[nodiscard]] VpxBitStreamWriter ComposeUncompressedHeader();
+
+ Host1x::Host1x& host1x;
+ std::vector<u8> frame;
+
+ std::array<s8, 4> loop_filter_ref_deltas{};
+ std::array<s8, 2> loop_filter_mode_deltas{};
+
+ Vp9FrameContainer next_frame{};
+ std::array<Vp9EntropyProbs, 4> frame_ctxs{};
+ bool swap_ref_indices{};
+
+ Vp9PictureInfo current_frame_info{};
+ Vp9EntropyProbs prev_frame_probs{};
+};
+
+} // namespace Decoder
+} // namespace Tegra
diff --git a/src/video_core/host1x/codecs/vp9_types.h b/src/video_core/host1x/codecs/vp9_types.h
new file mode 100644
index 000000000..adad8ed7e
--- /dev/null
+++ b/src/video_core/host1x/codecs/vp9_types.h
@@ -0,0 +1,305 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Tegra {
+
+namespace Decoder {
+struct Vp9FrameDimensions {
+ s16 width;
+ s16 height;
+ s16 luma_pitch;
+ s16 chroma_pitch;
+};
+static_assert(sizeof(Vp9FrameDimensions) == 0x8, "Vp9 Vp9FrameDimensions is an invalid size");
+
+enum class FrameFlags : u32 {
+ IsKeyFrame = 1 << 0,
+ LastFrameIsKeyFrame = 1 << 1,
+ FrameSizeChanged = 1 << 2,
+ ErrorResilientMode = 1 << 3,
+ LastShowFrame = 1 << 4,
+ IntraOnly = 1 << 5,
+};
+DECLARE_ENUM_FLAG_OPERATORS(FrameFlags)
+
+enum class TxSize {
+ Tx4x4 = 0, // 4x4 transform
+ Tx8x8 = 1, // 8x8 transform
+ Tx16x16 = 2, // 16x16 transform
+ Tx32x32 = 3, // 32x32 transform
+ TxSizes = 4
+};
+
+enum class TxMode {
+ Only4X4 = 0, // Only 4x4 transform used
+ Allow8X8 = 1, // Allow block transform size up to 8x8
+ Allow16X16 = 2, // Allow block transform size up to 16x16
+ Allow32X32 = 3, // Allow block transform size up to 32x32
+ TxModeSelect = 4, // Transform specified for each block
+ TxModes = 5
+};
+
+struct Segmentation {
+ u8 enabled;
+ u8 update_map;
+ u8 temporal_update;
+ u8 abs_delta;
+ std::array<u32, 8> feature_mask;
+ std::array<std::array<s16, 4>, 8> feature_data;
+};
+static_assert(sizeof(Segmentation) == 0x64, "Segmentation is an invalid size");
+
+struct LoopFilter {
+ u8 mode_ref_delta_enabled;
+ std::array<s8, 4> ref_deltas;
+ std::array<s8, 2> mode_deltas;
+};
+static_assert(sizeof(LoopFilter) == 0x7, "LoopFilter is an invalid size");
+
+struct Vp9EntropyProbs {
+ std::array<u8, 36> y_mode_prob; ///< 0x0000
+ std::array<u8, 64> partition_prob; ///< 0x0024
+ std::array<u8, 1728> coef_probs; ///< 0x0064
+ std::array<u8, 8> switchable_interp_prob; ///< 0x0724
+ std::array<u8, 28> inter_mode_prob; ///< 0x072C
+ std::array<u8, 4> intra_inter_prob; ///< 0x0748
+ std::array<u8, 5> comp_inter_prob; ///< 0x074C
+ std::array<u8, 10> single_ref_prob; ///< 0x0751
+ std::array<u8, 5> comp_ref_prob; ///< 0x075B
+ std::array<u8, 6> tx_32x32_prob; ///< 0x0760
+ std::array<u8, 4> tx_16x16_prob; ///< 0x0766
+ std::array<u8, 2> tx_8x8_prob; ///< 0x076A
+ std::array<u8, 3> skip_probs; ///< 0x076C
+ std::array<u8, 3> joints; ///< 0x076F
+ std::array<u8, 2> sign; ///< 0x0772
+ std::array<u8, 20> classes; ///< 0x0774
+ std::array<u8, 2> class_0; ///< 0x0788
+ std::array<u8, 20> prob_bits; ///< 0x078A
+ std::array<u8, 12> class_0_fr; ///< 0x079E
+ std::array<u8, 6> fr; ///< 0x07AA
+ std::array<u8, 2> class_0_hp; ///< 0x07B0
+ std::array<u8, 2> high_precision; ///< 0x07B2
+};
+static_assert(sizeof(Vp9EntropyProbs) == 0x7B4, "Vp9EntropyProbs is an invalid size");
+
+struct Vp9PictureInfo {
+ u32 bitstream_size;
+ std::array<u64, 4> frame_offsets;
+ std::array<s8, 4> ref_frame_sign_bias;
+ s32 base_q_index;
+ s32 y_dc_delta_q;
+ s32 uv_dc_delta_q;
+ s32 uv_ac_delta_q;
+ s32 transform_mode;
+ s32 interp_filter;
+ s32 reference_mode;
+ s32 log2_tile_cols;
+ s32 log2_tile_rows;
+ std::array<s8, 4> ref_deltas;
+ std::array<s8, 2> mode_deltas;
+ Vp9EntropyProbs entropy;
+ Vp9FrameDimensions frame_size;
+ u8 first_level;
+ u8 sharpness_level;
+ bool is_key_frame;
+ bool intra_only;
+ bool last_frame_was_key;
+ bool error_resilient_mode;
+ bool last_frame_shown;
+ bool show_frame;
+ bool lossless;
+ bool allow_high_precision_mv;
+ bool segment_enabled;
+ bool mode_ref_delta_enabled;
+};
+
+struct Vp9FrameContainer {
+ Vp9PictureInfo info{};
+ std::vector<u8> bit_stream;
+};
+
+struct PictureInfo {
+ INSERT_PADDING_WORDS_NOINIT(12); ///< 0x00
+ u32 bitstream_size; ///< 0x30
+ INSERT_PADDING_WORDS_NOINIT(5); ///< 0x34
+ Vp9FrameDimensions last_frame_size; ///< 0x48
+ Vp9FrameDimensions golden_frame_size; ///< 0x50
+ Vp9FrameDimensions alt_frame_size; ///< 0x58
+ Vp9FrameDimensions current_frame_size; ///< 0x60
+ FrameFlags vp9_flags; ///< 0x68
+ std::array<s8, 4> ref_frame_sign_bias; ///< 0x6C
+ u8 first_level; ///< 0x70
+ u8 sharpness_level; ///< 0x71
+ u8 base_q_index; ///< 0x72
+ u8 y_dc_delta_q; ///< 0x73
+ u8 uv_ac_delta_q; ///< 0x74
+ u8 uv_dc_delta_q; ///< 0x75
+ u8 lossless; ///< 0x76
+ u8 tx_mode; ///< 0x77
+ u8 allow_high_precision_mv; ///< 0x78
+ u8 interp_filter; ///< 0x79
+ u8 reference_mode; ///< 0x7A
+ INSERT_PADDING_BYTES_NOINIT(3); ///< 0x7B
+ u8 log2_tile_cols; ///< 0x7E
+ u8 log2_tile_rows; ///< 0x7F
+ Segmentation segmentation; ///< 0x80
+ LoopFilter loop_filter; ///< 0xE4
+ INSERT_PADDING_BYTES_NOINIT(21); ///< 0xEB
+
+ [[nodiscard]] Vp9PictureInfo Convert() const {
+ return {
+ .bitstream_size = bitstream_size,
+ .frame_offsets{},
+ .ref_frame_sign_bias = ref_frame_sign_bias,
+ .base_q_index = base_q_index,
+ .y_dc_delta_q = y_dc_delta_q,
+ .uv_dc_delta_q = uv_dc_delta_q,
+ .uv_ac_delta_q = uv_ac_delta_q,
+ .transform_mode = tx_mode,
+ .interp_filter = interp_filter,
+ .reference_mode = reference_mode,
+ .log2_tile_cols = log2_tile_cols,
+ .log2_tile_rows = log2_tile_rows,
+ .ref_deltas = loop_filter.ref_deltas,
+ .mode_deltas = loop_filter.mode_deltas,
+ .entropy{},
+ .frame_size = current_frame_size,
+ .first_level = first_level,
+ .sharpness_level = sharpness_level,
+ .is_key_frame = True(vp9_flags & FrameFlags::IsKeyFrame),
+ .intra_only = True(vp9_flags & FrameFlags::IntraOnly),
+ .last_frame_was_key = True(vp9_flags & FrameFlags::LastFrameIsKeyFrame),
+ .error_resilient_mode = True(vp9_flags & FrameFlags::ErrorResilientMode),
+ .last_frame_shown = True(vp9_flags & FrameFlags::LastShowFrame),
+ .show_frame = true,
+ .lossless = lossless != 0,
+ .allow_high_precision_mv = allow_high_precision_mv != 0,
+ .segment_enabled = segmentation.enabled != 0,
+ .mode_ref_delta_enabled = loop_filter.mode_ref_delta_enabled != 0,
+ };
+ }
+};
+static_assert(sizeof(PictureInfo) == 0x100, "PictureInfo is an invalid size");
+
+struct EntropyProbs {
+ INSERT_PADDING_BYTES_NOINIT(1024); ///< 0x0000
+ std::array<u8, 28> inter_mode_prob; ///< 0x0400
+ std::array<u8, 4> intra_inter_prob; ///< 0x041C
+ INSERT_PADDING_BYTES_NOINIT(80); ///< 0x0420
+ std::array<u8, 2> tx_8x8_prob; ///< 0x0470
+ std::array<u8, 4> tx_16x16_prob; ///< 0x0472
+ std::array<u8, 6> tx_32x32_prob; ///< 0x0476
+ std::array<u8, 4> y_mode_prob_e8; ///< 0x047C
+ std::array<std::array<u8, 8>, 4> y_mode_prob_e0e7; ///< 0x0480
+ INSERT_PADDING_BYTES_NOINIT(64); ///< 0x04A0
+ std::array<u8, 64> partition_prob; ///< 0x04E0
+ INSERT_PADDING_BYTES_NOINIT(10); ///< 0x0520
+ std::array<u8, 8> switchable_interp_prob; ///< 0x052A
+ std::array<u8, 5> comp_inter_prob; ///< 0x0532
+ std::array<u8, 3> skip_probs; ///< 0x0537
+ INSERT_PADDING_BYTES_NOINIT(1); ///< 0x053A
+ std::array<u8, 3> joints; ///< 0x053B
+ std::array<u8, 2> sign; ///< 0x053E
+ std::array<u8, 2> class_0; ///< 0x0540
+ std::array<u8, 6> fr; ///< 0x0542
+ std::array<u8, 2> class_0_hp; ///< 0x0548
+ std::array<u8, 2> high_precision; ///< 0x054A
+ std::array<u8, 20> classes; ///< 0x054C
+ std::array<u8, 12> class_0_fr; ///< 0x0560
+ std::array<u8, 20> pred_bits; ///< 0x056C
+ std::array<u8, 10> single_ref_prob; ///< 0x0580
+ std::array<u8, 5> comp_ref_prob; ///< 0x058A
+ INSERT_PADDING_BYTES_NOINIT(17); ///< 0x058F
+ std::array<u8, 2304> coef_probs; ///< 0x05A0
+
+ void Convert(Vp9EntropyProbs& fc) {
+ fc.inter_mode_prob = inter_mode_prob;
+ fc.intra_inter_prob = intra_inter_prob;
+ fc.tx_8x8_prob = tx_8x8_prob;
+ fc.tx_16x16_prob = tx_16x16_prob;
+ fc.tx_32x32_prob = tx_32x32_prob;
+
+ for (std::size_t i = 0; i < 4; i++) {
+ for (std::size_t j = 0; j < 9; j++) {
+ fc.y_mode_prob[j + 9 * i] = j < 8 ? y_mode_prob_e0e7[i][j] : y_mode_prob_e8[i];
+ }
+ }
+
+ fc.partition_prob = partition_prob;
+ fc.switchable_interp_prob = switchable_interp_prob;
+ fc.comp_inter_prob = comp_inter_prob;
+ fc.skip_probs = skip_probs;
+ fc.joints = joints;
+ fc.sign = sign;
+ fc.class_0 = class_0;
+ fc.fr = fr;
+ fc.class_0_hp = class_0_hp;
+ fc.high_precision = high_precision;
+ fc.classes = classes;
+ fc.class_0_fr = class_0_fr;
+ fc.prob_bits = pred_bits;
+ fc.single_ref_prob = single_ref_prob;
+ fc.comp_ref_prob = comp_ref_prob;
+
+ // Skip the 4th element as it goes unused
+ for (std::size_t i = 0; i < coef_probs.size(); i += 4) {
+ const std::size_t j = i - i / 4;
+ fc.coef_probs[j] = coef_probs[i];
+ fc.coef_probs[j + 1] = coef_probs[i + 1];
+ fc.coef_probs[j + 2] = coef_probs[i + 2];
+ }
+ }
+};
+static_assert(sizeof(EntropyProbs) == 0xEA0, "EntropyProbs is an invalid size");
+
+enum class Ref { Last, Golden, AltRef };
+
+struct RefPoolElement {
+ s64 frame{};
+ Ref ref{};
+ bool refresh{};
+};
+
+#define ASSERT_POSITION(field_name, position) \
+ static_assert(offsetof(Vp9EntropyProbs, field_name) == position, \
+ "Field " #field_name " has invalid position")
+
+ASSERT_POSITION(partition_prob, 0x0024);
+ASSERT_POSITION(switchable_interp_prob, 0x0724);
+ASSERT_POSITION(sign, 0x0772);
+ASSERT_POSITION(class_0_fr, 0x079E);
+ASSERT_POSITION(high_precision, 0x07B2);
+#undef ASSERT_POSITION
+
+#define ASSERT_POSITION(field_name, position) \
+ static_assert(offsetof(PictureInfo, field_name) == position, \
+ "Field " #field_name " has invalid position")
+
+ASSERT_POSITION(bitstream_size, 0x30);
+ASSERT_POSITION(last_frame_size, 0x48);
+ASSERT_POSITION(first_level, 0x70);
+ASSERT_POSITION(segmentation, 0x80);
+ASSERT_POSITION(loop_filter, 0xE4);
+#undef ASSERT_POSITION
+
+#define ASSERT_POSITION(field_name, position) \
+ static_assert(offsetof(EntropyProbs, field_name) == position, \
+ "Field " #field_name " has invalid position")
+
+ASSERT_POSITION(inter_mode_prob, 0x400);
+ASSERT_POSITION(tx_8x8_prob, 0x470);
+ASSERT_POSITION(partition_prob, 0x4E0);
+ASSERT_POSITION(class_0, 0x540);
+ASSERT_POSITION(class_0_fr, 0x560);
+ASSERT_POSITION(coef_probs, 0x5A0);
+#undef ASSERT_POSITION
+
+}; // namespace Decoder
+}; // namespace Tegra
diff --git a/src/video_core/host1x/control.cpp b/src/video_core/host1x/control.cpp
new file mode 100644
index 000000000..dceefdb7f
--- /dev/null
+++ b/src/video_core/host1x/control.cpp
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/assert.h"
+#include "video_core/host1x/control.h"
+#include "video_core/host1x/host1x.h"
+
+namespace Tegra::Host1x {
+
+Control::Control(Host1x& host1x_) : host1x(host1x_) {}
+
+Control::~Control() = default;
+
+void Control::ProcessMethod(Method method, u32 argument) {
+ switch (method) {
+ case Method::LoadSyncptPayload32:
+ syncpoint_value = argument;
+ break;
+ case Method::WaitSyncpt:
+ case Method::WaitSyncpt32:
+ Execute(argument);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Control method 0x{:X}", static_cast<u32>(method));
+ break;
+ }
+}
+
+void Control::Execute(u32 data) {
+ host1x.GetSyncpointManager().WaitHost(data, syncpoint_value);
+}
+
+} // namespace Tegra::Host1x
diff --git a/src/video_core/host1x/control.h b/src/video_core/host1x/control.h
new file mode 100644
index 000000000..e117888a3
--- /dev/null
+++ b/src/video_core/host1x/control.h
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Tegra {
+
+namespace Host1x {
+
+class Host1x;
+class Nvdec;
+
+class Control {
+public:
+ enum class Method : u32 {
+ WaitSyncpt = 0x8,
+ LoadSyncptPayload32 = 0x4e,
+ WaitSyncpt32 = 0x50,
+ };
+
+ explicit Control(Host1x& host1x);
+ ~Control();
+
+ /// Writes the method into the state, Invoke Execute() if encountered
+ void ProcessMethod(Method method, u32 argument);
+
+private:
+ /// For Host1x, execute is waiting on a syncpoint previously written into the state
+ void Execute(u32 data);
+
+ u32 syncpoint_value{};
+ Host1x& host1x;
+};
+
+} // namespace Host1x
+
+} // namespace Tegra
diff --git a/src/video_core/host1x/host1x.cpp b/src/video_core/host1x/host1x.cpp
new file mode 100644
index 000000000..7c317a85d
--- /dev/null
+++ b/src/video_core/host1x/host1x.cpp
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/core.h"
+#include "video_core/host1x/host1x.h"
+
+namespace Tegra {
+
+namespace Host1x {
+
+Host1x::Host1x(Core::System& system_)
+ : system{system_}, syncpoint_manager{}, memory_manager{system, 32, 12},
+ allocator{std::make_unique<Common::FlatAllocator<u32, 0, 32>>(1 << 12)} {}
+
+} // namespace Host1x
+
+} // namespace Tegra
diff --git a/src/video_core/host1x/host1x.h b/src/video_core/host1x/host1x.h
new file mode 100644
index 000000000..57082ae54
--- /dev/null
+++ b/src/video_core/host1x/host1x.h
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+#include "common/address_space.h"
+#include "video_core/host1x/syncpoint_manager.h"
+#include "video_core/memory_manager.h"
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace Tegra {
+
+namespace Host1x {
+
+class Host1x {
+public:
+ explicit Host1x(Core::System& system);
+
+ SyncpointManager& GetSyncpointManager() {
+ return syncpoint_manager;
+ }
+
+ const SyncpointManager& GetSyncpointManager() const {
+ return syncpoint_manager;
+ }
+
+ Tegra::MemoryManager& MemoryManager() {
+ return memory_manager;
+ }
+
+ const Tegra::MemoryManager& MemoryManager() const {
+ return memory_manager;
+ }
+
+ Common::FlatAllocator<u32, 0, 32>& Allocator() {
+ return *allocator;
+ }
+
+ const Common::FlatAllocator<u32, 0, 32>& Allocator() const {
+ return *allocator;
+ }
+
+private:
+ Core::System& system;
+ SyncpointManager syncpoint_manager;
+ Tegra::MemoryManager memory_manager;
+ std::unique_ptr<Common::FlatAllocator<u32, 0, 32>> allocator;
+};
+
+} // namespace Host1x
+
+} // namespace Tegra
diff --git a/src/video_core/host1x/nvdec.cpp b/src/video_core/host1x/nvdec.cpp
new file mode 100644
index 000000000..a4bd5b79f
--- /dev/null
+++ b/src/video_core/host1x/nvdec.cpp
@@ -0,0 +1,48 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/assert.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/host1x/nvdec.h"
+
+namespace Tegra::Host1x {
+
+#define NVDEC_REG_INDEX(field_name) \
+ (offsetof(NvdecCommon::NvdecRegisters, field_name) / sizeof(u64))
+
+Nvdec::Nvdec(Host1x& host1x_)
+ : host1x(host1x_), state{}, codec(std::make_unique<Codec>(host1x, state)) {}
+
+Nvdec::~Nvdec() = default;
+
+void Nvdec::ProcessMethod(u32 method, u32 argument) {
+ state.reg_array[method] = static_cast<u64>(argument) << 8;
+
+ switch (method) {
+ case NVDEC_REG_INDEX(set_codec_id):
+ codec->SetTargetCodec(static_cast<NvdecCommon::VideoCodec>(argument));
+ break;
+ case NVDEC_REG_INDEX(execute):
+ Execute();
+ break;
+ }
+}
+
+AVFramePtr Nvdec::GetFrame() {
+ return codec->GetCurrentFrame();
+}
+
+void Nvdec::Execute() {
+ switch (codec->GetCurrentCodec()) {
+ case NvdecCommon::VideoCodec::H264:
+ case NvdecCommon::VideoCodec::VP8:
+ case NvdecCommon::VideoCodec::VP9:
+ codec->Decode();
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Codec {}", codec->GetCurrentCodecName());
+ break;
+ }
+}
+
+} // namespace Tegra::Host1x
diff --git a/src/video_core/host1x/nvdec.h b/src/video_core/host1x/nvdec.h
new file mode 100644
index 000000000..3949d5181
--- /dev/null
+++ b/src/video_core/host1x/nvdec.h
@@ -0,0 +1,39 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include "common/common_types.h"
+#include "video_core/host1x/codecs/codec.h"
+
+namespace Tegra {
+
+namespace Host1x {
+
+class Host1x;
+
+class Nvdec {
+public:
+ explicit Nvdec(Host1x& host1x);
+ ~Nvdec();
+
+ /// Writes the method into the state, Invoke Execute() if encountered
+ void ProcessMethod(u32 method, u32 argument);
+
+ /// Return most recently decoded frame
+ [[nodiscard]] AVFramePtr GetFrame();
+
+private:
+ /// Invoke codec to decode a frame
+ void Execute();
+
+ Host1x& host1x;
+ NvdecCommon::NvdecRegisters state;
+ std::unique_ptr<Codec> codec;
+};
+
+} // namespace Host1x
+
+} // namespace Tegra
diff --git a/src/video_core/host1x/nvdec_common.h b/src/video_core/host1x/nvdec_common.h
new file mode 100644
index 000000000..49d67ebbe
--- /dev/null
+++ b/src/video_core/host1x/nvdec_common.h
@@ -0,0 +1,97 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Tegra::Host1x::NvdecCommon {
+
+enum class VideoCodec : u64 {
+ None = 0x0,
+ H264 = 0x3,
+ VP8 = 0x5,
+ H265 = 0x7,
+ VP9 = 0x9,
+};
+
+// NVDEC should use a 32-bit address space, but is mapped to 64-bit,
+// doubling the sizes here is compensating for that.
+struct NvdecRegisters {
+ static constexpr std::size_t NUM_REGS = 0x178;
+
+ union {
+ struct {
+ INSERT_PADDING_WORDS_NOINIT(256); ///< 0x0000
+ VideoCodec set_codec_id; ///< 0x0400
+ INSERT_PADDING_WORDS_NOINIT(126); ///< 0x0408
+ u64 execute; ///< 0x0600
+ INSERT_PADDING_WORDS_NOINIT(126); ///< 0x0608
+ struct { ///< 0x0800
+ union {
+ BitField<0, 3, VideoCodec> codec;
+ BitField<4, 1, u64> gp_timer_on;
+ BitField<13, 1, u64> mb_timer_on;
+ BitField<14, 1, u64> intra_frame_pslc;
+ BitField<17, 1, u64> all_intra_frame;
+ };
+ } control_params;
+ u64 picture_info_offset; ///< 0x0808
+ u64 frame_bitstream_offset; ///< 0x0810
+ u64 frame_number; ///< 0x0818
+ u64 h264_slice_data_offsets; ///< 0x0820
+ u64 h264_mv_dump_offset; ///< 0x0828
+ INSERT_PADDING_WORDS_NOINIT(6); ///< 0x0830
+ u64 frame_stats_offset; ///< 0x0848
+ u64 h264_last_surface_luma_offset; ///< 0x0850
+ u64 h264_last_surface_chroma_offset; ///< 0x0858
+ std::array<u64, 17> surface_luma_offset; ///< 0x0860
+ std::array<u64, 17> surface_chroma_offset; ///< 0x08E8
+ INSERT_PADDING_WORDS_NOINIT(68); ///< 0x0970
+ u64 vp8_prob_data_offset; ///< 0x0A80
+ u64 vp8_header_partition_buf_offset; ///< 0x0A88
+ INSERT_PADDING_WORDS_NOINIT(60); ///< 0x0A90
+ u64 vp9_entropy_probs_offset; ///< 0x0B80
+ u64 vp9_backward_updates_offset; ///< 0x0B88
+ u64 vp9_last_frame_segmap_offset; ///< 0x0B90
+ u64 vp9_curr_frame_segmap_offset; ///< 0x0B98
+ INSERT_PADDING_WORDS_NOINIT(2); ///< 0x0BA0
+ u64 vp9_last_frame_mvs_offset; ///< 0x0BA8
+ u64 vp9_curr_frame_mvs_offset; ///< 0x0BB0
+ INSERT_PADDING_WORDS_NOINIT(2); ///< 0x0BB8
+ };
+ std::array<u64, NUM_REGS> reg_array;
+ };
+};
+static_assert(sizeof(NvdecRegisters) == (0xBC0), "NvdecRegisters is incorrect size");
+
+#define ASSERT_REG_POSITION(field_name, position) \
+ static_assert(offsetof(NvdecRegisters, field_name) == position * sizeof(u64), \
+ "Field " #field_name " has invalid position")
+
+ASSERT_REG_POSITION(set_codec_id, 0x80);
+ASSERT_REG_POSITION(execute, 0xC0);
+ASSERT_REG_POSITION(control_params, 0x100);
+ASSERT_REG_POSITION(picture_info_offset, 0x101);
+ASSERT_REG_POSITION(frame_bitstream_offset, 0x102);
+ASSERT_REG_POSITION(frame_number, 0x103);
+ASSERT_REG_POSITION(h264_slice_data_offsets, 0x104);
+ASSERT_REG_POSITION(frame_stats_offset, 0x109);
+ASSERT_REG_POSITION(h264_last_surface_luma_offset, 0x10A);
+ASSERT_REG_POSITION(h264_last_surface_chroma_offset, 0x10B);
+ASSERT_REG_POSITION(surface_luma_offset, 0x10C);
+ASSERT_REG_POSITION(surface_chroma_offset, 0x11D);
+ASSERT_REG_POSITION(vp8_prob_data_offset, 0x150);
+ASSERT_REG_POSITION(vp8_header_partition_buf_offset, 0x151);
+ASSERT_REG_POSITION(vp9_entropy_probs_offset, 0x170);
+ASSERT_REG_POSITION(vp9_backward_updates_offset, 0x171);
+ASSERT_REG_POSITION(vp9_last_frame_segmap_offset, 0x172);
+ASSERT_REG_POSITION(vp9_curr_frame_segmap_offset, 0x173);
+ASSERT_REG_POSITION(vp9_last_frame_mvs_offset, 0x175);
+ASSERT_REG_POSITION(vp9_curr_frame_mvs_offset, 0x176);
+
+#undef ASSERT_REG_POSITION
+
+} // namespace Tegra::Host1x::NvdecCommon
diff --git a/src/video_core/host1x/sync_manager.cpp b/src/video_core/host1x/sync_manager.cpp
new file mode 100644
index 000000000..5ef9ea217
--- /dev/null
+++ b/src/video_core/host1x/sync_manager.cpp
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: Ryujinx Team and Contributors
+// SPDX-License-Identifier: MIT
+
+#include <algorithm>
+#include "sync_manager.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/host1x/syncpoint_manager.h"
+
+namespace Tegra {
+namespace Host1x {
+
+SyncptIncrManager::SyncptIncrManager(Host1x& host1x_) : host1x(host1x_) {}
+SyncptIncrManager::~SyncptIncrManager() = default;
+
+void SyncptIncrManager::Increment(u32 id) {
+ increments.emplace_back(0, 0, id, true);
+ IncrementAllDone();
+}
+
+u32 SyncptIncrManager::IncrementWhenDone(u32 class_id, u32 id) {
+ const u32 handle = current_id++;
+ increments.emplace_back(handle, class_id, id);
+ return handle;
+}
+
+void SyncptIncrManager::SignalDone(u32 handle) {
+ const auto done_incr =
+ std::find_if(increments.begin(), increments.end(),
+ [handle](const SyncptIncr& incr) { return incr.id == handle; });
+ if (done_incr != increments.cend()) {
+ done_incr->complete = true;
+ }
+ IncrementAllDone();
+}
+
+void SyncptIncrManager::IncrementAllDone() {
+ std::size_t done_count = 0;
+ for (; done_count < increments.size(); ++done_count) {
+ if (!increments[done_count].complete) {
+ break;
+ }
+ auto& syncpoint_manager = host1x.GetSyncpointManager();
+ syncpoint_manager.IncrementGuest(increments[done_count].syncpt_id);
+ syncpoint_manager.IncrementHost(increments[done_count].syncpt_id);
+ }
+ increments.erase(increments.begin(), increments.begin() + done_count);
+}
+
+} // namespace Host1x
+} // namespace Tegra
diff --git a/src/video_core/host1x/sync_manager.h b/src/video_core/host1x/sync_manager.h
new file mode 100644
index 000000000..7bb77fa27
--- /dev/null
+++ b/src/video_core/host1x/sync_manager.h
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: Ryujinx Team and Contributors
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <mutex>
+#include <vector>
+#include "common/common_types.h"
+
+namespace Tegra {
+
+namespace Host1x {
+
+class Host1x;
+
+struct SyncptIncr {
+ u32 id;
+ u32 class_id;
+ u32 syncpt_id;
+ bool complete;
+
+ SyncptIncr(u32 id_, u32 class_id_, u32 syncpt_id_, bool done = false)
+ : id(id_), class_id(class_id_), syncpt_id(syncpt_id_), complete(done) {}
+};
+
+class SyncptIncrManager {
+public:
+ explicit SyncptIncrManager(Host1x& host1x);
+ ~SyncptIncrManager();
+
+ /// Add syncpoint id and increment all
+ void Increment(u32 id);
+
+ /// Returns a handle to increment later
+ u32 IncrementWhenDone(u32 class_id, u32 id);
+
+ /// IncrememntAllDone, including handle
+ void SignalDone(u32 handle);
+
+ /// Increment all sequential pending increments that are already done.
+ void IncrementAllDone();
+
+private:
+ std::vector<SyncptIncr> increments;
+ std::mutex increment_lock;
+ u32 current_id{};
+
+ Host1x& host1x;
+};
+
+} // namespace Host1x
+
+} // namespace Tegra
diff --git a/src/video_core/host1x/syncpoint_manager.cpp b/src/video_core/host1x/syncpoint_manager.cpp
new file mode 100644
index 000000000..326e8355a
--- /dev/null
+++ b/src/video_core/host1x/syncpoint_manager.cpp
@@ -0,0 +1,96 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/microprofile.h"
+#include "video_core/host1x/syncpoint_manager.h"
+
+namespace Tegra {
+
+namespace Host1x {
+
+MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192));
+
+SyncpointManager::ActionHandle SyncpointManager::RegisterAction(
+ std::atomic<u32>& syncpoint, std::list<RegisteredAction>& action_storage, u32 expected_value,
+ std::function<void()>&& action) {
+ if (syncpoint.load(std::memory_order_acquire) >= expected_value) {
+ action();
+ return {};
+ }
+
+ std::unique_lock lk(guard);
+ if (syncpoint.load(std::memory_order_relaxed) >= expected_value) {
+ action();
+ return {};
+ }
+ auto it = action_storage.begin();
+ while (it != action_storage.end()) {
+ if (it->expected_value >= expected_value) {
+ break;
+ }
+ ++it;
+ }
+ return action_storage.emplace(it, expected_value, std::move(action));
+}
+
+void SyncpointManager::DeregisterAction(std::list<RegisteredAction>& action_storage,
+ ActionHandle& handle) {
+ std::unique_lock lk(guard);
+ action_storage.erase(handle);
+}
+
+void SyncpointManager::DeregisterGuestAction(u32 syncpoint_id, ActionHandle& handle) {
+ DeregisterAction(guest_action_storage[syncpoint_id], handle);
+}
+
+void SyncpointManager::DeregisterHostAction(u32 syncpoint_id, ActionHandle& handle) {
+ DeregisterAction(host_action_storage[syncpoint_id], handle);
+}
+
+void SyncpointManager::IncrementGuest(u32 syncpoint_id) {
+ Increment(syncpoints_guest[syncpoint_id], wait_guest_cv, guest_action_storage[syncpoint_id]);
+}
+
+void SyncpointManager::IncrementHost(u32 syncpoint_id) {
+ Increment(syncpoints_host[syncpoint_id], wait_host_cv, host_action_storage[syncpoint_id]);
+}
+
+void SyncpointManager::WaitGuest(u32 syncpoint_id, u32 expected_value) {
+ Wait(syncpoints_guest[syncpoint_id], wait_guest_cv, expected_value);
+}
+
+void SyncpointManager::WaitHost(u32 syncpoint_id, u32 expected_value) {
+ MICROPROFILE_SCOPE(GPU_wait);
+ Wait(syncpoints_host[syncpoint_id], wait_host_cv, expected_value);
+}
+
+void SyncpointManager::Increment(std::atomic<u32>& syncpoint, std::condition_variable& wait_cv,
+ std::list<RegisteredAction>& action_storage) {
+ auto new_value{syncpoint.fetch_add(1, std::memory_order_acq_rel) + 1};
+
+ std::unique_lock lk(guard);
+ auto it = action_storage.begin();
+ while (it != action_storage.end()) {
+ if (it->expected_value > new_value) {
+ break;
+ }
+ it->action();
+ it = action_storage.erase(it);
+ }
+ wait_cv.notify_all();
+}
+
+void SyncpointManager::Wait(std::atomic<u32>& syncpoint, std::condition_variable& wait_cv,
+ u32 expected_value) {
+ const auto pred = [&]() { return syncpoint.load(std::memory_order_acquire) >= expected_value; };
+ if (pred()) {
+ return;
+ }
+
+ std::unique_lock lk(guard);
+ wait_cv.wait(lk, pred);
+}
+
+} // namespace Host1x
+
+} // namespace Tegra
diff --git a/src/video_core/host1x/syncpoint_manager.h b/src/video_core/host1x/syncpoint_manager.h
new file mode 100644
index 000000000..50a264e23
--- /dev/null
+++ b/src/video_core/host1x/syncpoint_manager.h
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <condition_variable>
+#include <functional>
+#include <list>
+#include <mutex>
+
+#include "common/common_types.h"
+
+namespace Tegra {
+
+namespace Host1x {
+
+class SyncpointManager {
+public:
+ u32 GetGuestSyncpointValue(u32 id) const {
+ return syncpoints_guest[id].load(std::memory_order_acquire);
+ }
+
+ u32 GetHostSyncpointValue(u32 id) const {
+ return syncpoints_host[id].load(std::memory_order_acquire);
+ }
+
+ struct RegisteredAction {
+ explicit RegisteredAction(u32 expected_value_, std::function<void()>&& action_)
+ : expected_value{expected_value_}, action{std::move(action_)} {}
+ u32 expected_value;
+ std::function<void()> action;
+ };
+ using ActionHandle = std::list<RegisteredAction>::iterator;
+
+ template <typename Func>
+ ActionHandle RegisterGuestAction(u32 syncpoint_id, u32 expected_value, Func&& action) {
+ std::function<void()> func(action);
+ return RegisterAction(syncpoints_guest[syncpoint_id], guest_action_storage[syncpoint_id],
+ expected_value, std::move(func));
+ }
+
+ template <typename Func>
+ ActionHandle RegisterHostAction(u32 syncpoint_id, u32 expected_value, Func&& action) {
+ std::function<void()> func(action);
+ return RegisterAction(syncpoints_host[syncpoint_id], host_action_storage[syncpoint_id],
+ expected_value, std::move(func));
+ }
+
+ void DeregisterGuestAction(u32 syncpoint_id, ActionHandle& handle);
+
+ void DeregisterHostAction(u32 syncpoint_id, ActionHandle& handle);
+
+ void IncrementGuest(u32 syncpoint_id);
+
+ void IncrementHost(u32 syncpoint_id);
+
+ void WaitGuest(u32 syncpoint_id, u32 expected_value);
+
+ void WaitHost(u32 syncpoint_id, u32 expected_value);
+
+ bool IsReadyGuest(u32 syncpoint_id, u32 expected_value) const {
+ return syncpoints_guest[syncpoint_id].load(std::memory_order_acquire) >= expected_value;
+ }
+
+ bool IsReadyHost(u32 syncpoint_id, u32 expected_value) const {
+ return syncpoints_host[syncpoint_id].load(std::memory_order_acquire) >= expected_value;
+ }
+
+private:
+ void Increment(std::atomic<u32>& syncpoint, std::condition_variable& wait_cv,
+ std::list<RegisteredAction>& action_storage);
+
+ ActionHandle RegisterAction(std::atomic<u32>& syncpoint,
+ std::list<RegisteredAction>& action_storage, u32 expected_value,
+ std::function<void()>&& action);
+
+ void DeregisterAction(std::list<RegisteredAction>& action_storage, ActionHandle& handle);
+
+ void Wait(std::atomic<u32>& syncpoint, std::condition_variable& wait_cv, u32 expected_value);
+
+ static constexpr size_t NUM_MAX_SYNCPOINTS = 192;
+
+ std::array<std::atomic<u32>, NUM_MAX_SYNCPOINTS> syncpoints_guest{};
+ std::array<std::atomic<u32>, NUM_MAX_SYNCPOINTS> syncpoints_host{};
+
+ std::array<std::list<RegisteredAction>, NUM_MAX_SYNCPOINTS> guest_action_storage;
+ std::array<std::list<RegisteredAction>, NUM_MAX_SYNCPOINTS> host_action_storage;
+
+ std::mutex guard;
+ std::condition_variable wait_guest_cv;
+ std::condition_variable wait_host_cv;
+};
+
+} // namespace Host1x
+
+} // namespace Tegra
diff --git a/src/video_core/host1x/vic.cpp b/src/video_core/host1x/vic.cpp
new file mode 100644
index 000000000..ac0b7d20e
--- /dev/null
+++ b/src/video_core/host1x/vic.cpp
@@ -0,0 +1,244 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+
+extern "C" {
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wconversion"
+#endif
+#include <libswscale/swscale.h>
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+}
+
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/logging/log.h"
+
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/host1x/nvdec.h"
+#include "video_core/host1x/vic.h"
+#include "video_core/memory_manager.h"
+#include "video_core/textures/decoders.h"
+
+namespace Tegra {
+
+namespace Host1x {
+
+namespace {
+enum class VideoPixelFormat : u64_le {
+ RGBA8 = 0x1f,
+ BGRA8 = 0x20,
+ RGBX8 = 0x23,
+ YUV420 = 0x44,
+};
+} // Anonymous namespace
+
+union VicConfig {
+ u64_le raw{};
+ BitField<0, 7, VideoPixelFormat> pixel_format;
+ BitField<7, 2, u64_le> chroma_loc_horiz;
+ BitField<9, 2, u64_le> chroma_loc_vert;
+ BitField<11, 4, u64_le> block_linear_kind;
+ BitField<15, 4, u64_le> block_linear_height_log2;
+ BitField<32, 14, u64_le> surface_width_minus1;
+ BitField<46, 14, u64_le> surface_height_minus1;
+};
+
+Vic::Vic(Host1x& host1x_, std::shared_ptr<Nvdec> nvdec_processor_)
+ : host1x(host1x_),
+ nvdec_processor(std::move(nvdec_processor_)), converted_frame_buffer{nullptr, av_free} {}
+
+Vic::~Vic() = default;
+
+void Vic::ProcessMethod(Method method, u32 argument) {
+ LOG_DEBUG(HW_GPU, "Vic method 0x{:X}", static_cast<u32>(method));
+ const u64 arg = static_cast<u64>(argument) << 8;
+ switch (method) {
+ case Method::Execute:
+ Execute();
+ break;
+ case Method::SetConfigStructOffset:
+ config_struct_address = arg;
+ break;
+ case Method::SetOutputSurfaceLumaOffset:
+ output_surface_luma_address = arg;
+ break;
+ case Method::SetOutputSurfaceChromaOffset:
+ output_surface_chroma_address = arg;
+ break;
+ default:
+ break;
+ }
+}
+
+void Vic::Execute() {
+ if (output_surface_luma_address == 0) {
+ LOG_ERROR(Service_NVDRV, "VIC Luma address not set.");
+ return;
+ }
+ const VicConfig config{host1x.MemoryManager().Read<u64>(config_struct_address + 0x20)};
+ const AVFramePtr frame_ptr = nvdec_processor->GetFrame();
+ const auto* frame = frame_ptr.get();
+ if (!frame) {
+ return;
+ }
+ const u64 surface_width = config.surface_width_minus1 + 1;
+ const u64 surface_height = config.surface_height_minus1 + 1;
+ if (static_cast<u64>(frame->width) != surface_width ||
+ static_cast<u64>(frame->height) != surface_height) {
+ // TODO: Properly support multiple video streams with differing frame dimensions
+ LOG_WARNING(Service_NVDRV, "Frame dimensions {}x{} don't match surface dimensions {}x{}",
+ frame->width, frame->height, surface_width, surface_height);
+ }
+ switch (config.pixel_format) {
+ case VideoPixelFormat::RGBA8:
+ case VideoPixelFormat::BGRA8:
+ case VideoPixelFormat::RGBX8:
+ WriteRGBFrame(frame, config);
+ break;
+ case VideoPixelFormat::YUV420:
+ WriteYUVFrame(frame, config);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unknown video pixel format {:X}", config.pixel_format.Value());
+ break;
+ }
+}
+
+void Vic::WriteRGBFrame(const AVFrame* frame, const VicConfig& config) {
+ LOG_TRACE(Service_NVDRV, "Writing RGB Frame");
+
+ if (!scaler_ctx || frame->width != scaler_width || frame->height != scaler_height) {
+ const AVPixelFormat target_format = [pixel_format = config.pixel_format]() {
+ switch (pixel_format) {
+ case VideoPixelFormat::RGBA8:
+ return AV_PIX_FMT_RGBA;
+ case VideoPixelFormat::BGRA8:
+ return AV_PIX_FMT_BGRA;
+ case VideoPixelFormat::RGBX8:
+ return AV_PIX_FMT_RGB0;
+ default:
+ return AV_PIX_FMT_RGBA;
+ }
+ }();
+
+ sws_freeContext(scaler_ctx);
+ // Frames are decoded into either YUV420 or NV12 formats. Convert to desired RGB format
+ scaler_ctx = sws_getContext(frame->width, frame->height,
+ static_cast<AVPixelFormat>(frame->format), frame->width,
+ frame->height, target_format, 0, nullptr, nullptr, nullptr);
+ scaler_width = frame->width;
+ scaler_height = frame->height;
+ converted_frame_buffer.reset();
+ }
+ if (!converted_frame_buffer) {
+ const size_t frame_size = frame->width * frame->height * 4;
+ converted_frame_buffer = AVMallocPtr{static_cast<u8*>(av_malloc(frame_size)), av_free};
+ }
+ const std::array<int, 4> converted_stride{frame->width * 4, frame->height * 4, 0, 0};
+ u8* const converted_frame_buf_addr{converted_frame_buffer.get()};
+ sws_scale(scaler_ctx, frame->data, frame->linesize, 0, frame->height, &converted_frame_buf_addr,
+ converted_stride.data());
+
+ // Use the minimum of surface/frame dimensions to avoid buffer overflow.
+ const u32 surface_width = static_cast<u32>(config.surface_width_minus1) + 1;
+ const u32 surface_height = static_cast<u32>(config.surface_height_minus1) + 1;
+ const u32 width = std::min(surface_width, static_cast<u32>(frame->width));
+ const u32 height = std::min(surface_height, static_cast<u32>(frame->height));
+ const u32 blk_kind = static_cast<u32>(config.block_linear_kind);
+ if (blk_kind != 0) {
+ // swizzle pitch linear to block linear
+ const u32 block_height = static_cast<u32>(config.block_linear_height_log2);
+ const auto size = Texture::CalculateSize(true, 4, width, height, 1, block_height, 0);
+ luma_buffer.resize(size);
+ std::span<const u8> frame_buff(converted_frame_buf_addr, 4 * width * height);
+ Texture::SwizzleSubrect(luma_buffer, frame_buff, 4, width, height, 1, 0, 0, width, height,
+ block_height, 0, width * 4);
+
+ host1x.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(), size);
+ } else {
+ // send pitch linear frame
+ const size_t linear_size = width * height * 4;
+ host1x.MemoryManager().WriteBlock(output_surface_luma_address, converted_frame_buf_addr,
+ linear_size);
+ }
+}
+
+void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) {
+ LOG_TRACE(Service_NVDRV, "Writing YUV420 Frame");
+
+ const std::size_t surface_width = config.surface_width_minus1 + 1;
+ const std::size_t surface_height = config.surface_height_minus1 + 1;
+ const std::size_t aligned_width = (surface_width + 0xff) & ~0xffUL;
+ // Use the minimum of surface/frame dimensions to avoid buffer overflow.
+ const auto frame_width = std::min(surface_width, static_cast<size_t>(frame->width));
+ const auto frame_height = std::min(surface_height, static_cast<size_t>(frame->height));
+
+ const auto stride = static_cast<size_t>(frame->linesize[0]);
+
+ luma_buffer.resize(aligned_width * surface_height);
+ chroma_buffer.resize(aligned_width * surface_height / 2);
+
+ // Populate luma buffer
+ const u8* luma_src = frame->data[0];
+ for (std::size_t y = 0; y < frame_height; ++y) {
+ const std::size_t src = y * stride;
+ const std::size_t dst = y * aligned_width;
+ for (std::size_t x = 0; x < frame_width; ++x) {
+ luma_buffer[dst + x] = luma_src[src + x];
+ }
+ }
+ host1x.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(),
+ luma_buffer.size());
+
+ // Chroma
+ const std::size_t half_height = frame_height / 2;
+ const auto half_stride = static_cast<size_t>(frame->linesize[1]);
+
+ switch (frame->format) {
+ case AV_PIX_FMT_YUV420P: {
+ // Frame from FFmpeg software
+ // Populate chroma buffer from both channels with interleaving.
+ const std::size_t half_width = frame_width / 2;
+ const u8* chroma_b_src = frame->data[1];
+ const u8* chroma_r_src = frame->data[2];
+ for (std::size_t y = 0; y < half_height; ++y) {
+ const std::size_t src = y * half_stride;
+ const std::size_t dst = y * aligned_width;
+
+ for (std::size_t x = 0; x < half_width; ++x) {
+ chroma_buffer[dst + x * 2] = chroma_b_src[src + x];
+ chroma_buffer[dst + x * 2 + 1] = chroma_r_src[src + x];
+ }
+ }
+ break;
+ }
+ case AV_PIX_FMT_NV12: {
+ // Frame from VA-API hardware
+ // This is already interleaved so just copy
+ const u8* chroma_src = frame->data[1];
+ for (std::size_t y = 0; y < half_height; ++y) {
+ const std::size_t src = y * stride;
+ const std::size_t dst = y * aligned_width;
+ for (std::size_t x = 0; x < frame_width; ++x) {
+ chroma_buffer[dst + x] = chroma_src[src + x];
+ }
+ }
+ break;
+ }
+ default:
+ ASSERT(false);
+ break;
+ }
+ host1x.MemoryManager().WriteBlock(output_surface_chroma_address, chroma_buffer.data(),
+ chroma_buffer.size());
+}
+
+} // namespace Host1x
+
+} // namespace Tegra
diff --git a/src/video_core/host1x/vic.h b/src/video_core/host1x/vic.h
new file mode 100644
index 000000000..2b78786e8
--- /dev/null
+++ b/src/video_core/host1x/vic.h
@@ -0,0 +1,66 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include "common/common_types.h"
+
+struct SwsContext;
+
+namespace Tegra {
+
+namespace Host1x {
+
+class Host1x;
+class Nvdec;
+union VicConfig;
+
+class Vic {
+public:
+ enum class Method : u32 {
+ Execute = 0xc0,
+ SetControlParams = 0x1c1,
+ SetConfigStructOffset = 0x1c2,
+ SetOutputSurfaceLumaOffset = 0x1c8,
+ SetOutputSurfaceChromaOffset = 0x1c9,
+ SetOutputSurfaceChromaUnusedOffset = 0x1ca
+ };
+
+ explicit Vic(Host1x& host1x, std::shared_ptr<Nvdec> nvdec_processor);
+
+ ~Vic();
+
+ /// Write to the device state.
+ void ProcessMethod(Method method, u32 argument);
+
+private:
+ void Execute();
+
+ void WriteRGBFrame(const AVFrame* frame, const VicConfig& config);
+
+ void WriteYUVFrame(const AVFrame* frame, const VicConfig& config);
+
+ Host1x& host1x;
+ std::shared_ptr<Tegra::Host1x::Nvdec> nvdec_processor;
+
+ /// Avoid reallocation of the following buffers every frame, as their
+ /// size does not change during a stream
+ using AVMallocPtr = std::unique_ptr<u8, decltype(&av_free)>;
+ AVMallocPtr converted_frame_buffer;
+ std::vector<u8> luma_buffer;
+ std::vector<u8> chroma_buffer;
+
+ GPUVAddr config_struct_address{};
+ GPUVAddr output_surface_luma_address{};
+ GPUVAddr output_surface_chroma_address{};
+
+ SwsContext* scaler_ctx{};
+ s32 scaler_width{};
+ s32 scaler_height{};
+};
+
+} // namespace Host1x
+
+} // namespace Tegra
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index fd3e41434..2149ab93e 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr)
set(GLSL_INCLUDES
@@ -14,9 +17,11 @@ set(SHADER_FILES
convert_d24s8_to_abgr8.frag
convert_depth_to_float.frag
convert_float_to_depth.frag
+ convert_s8d24_to_abgr8.frag
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/StringShaderHeader.cmake b/src/video_core/host_shaders/StringShaderHeader.cmake
index 1b4bc6103..9f7525535 100644
--- a/src/video_core/host_shaders/StringShaderHeader.cmake
+++ b/src/video_core/host_shaders/StringShaderHeader.cmake
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: 2020 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
set(SOURCE_FILE ${CMAKE_ARGV3})
set(HEADER_FILE ${CMAKE_ARGV4})
set(INPUT_FILE ${CMAKE_ARGV5})
diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp
index 3a10578cb..d608678a3 100644
--- a/src/video_core/host_shaders/astc_decoder.comp
+++ b/src/video_core/host_shaders/astc_decoder.comp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 450
@@ -1066,7 +1065,7 @@ TexelWeightParams DecodeBlockInfo() {
void FillError(ivec3 coord) {
for (uint j = 0; j < block_dims.y; j++) {
for (uint i = 0; i < block_dims.x; i++) {
- imageStore(dest_image, coord + ivec3(i, j, 0), vec4(1.0, 1.0, 0.0, 1.0));
+ imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0));
}
}
}
diff --git a/src/video_core/host_shaders/block_linear_unswizzle_2d.comp b/src/video_core/host_shaders/block_linear_unswizzle_2d.comp
index a131be79e..121f60cbd 100644
--- a/src/video_core/host_shaders/block_linear_unswizzle_2d.comp
+++ b/src/video_core/host_shaders/block_linear_unswizzle_2d.comp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 430
diff --git a/src/video_core/host_shaders/block_linear_unswizzle_3d.comp b/src/video_core/host_shaders/block_linear_unswizzle_3d.comp
index bb6872e6b..7fc98accb 100644
--- a/src/video_core/host_shaders/block_linear_unswizzle_3d.comp
+++ b/src/video_core/host_shaders/block_linear_unswizzle_3d.comp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 430
diff --git a/src/video_core/host_shaders/convert_abgr8_to_d24s8.frag b/src/video_core/host_shaders/convert_abgr8_to_d24s8.frag
index ea055ddad..ff102e582 100644
--- a/src/video_core/host_shaders/convert_abgr8_to_d24s8.frag
+++ b/src/video_core/host_shaders/convert_abgr8_to_d24s8.frag
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 450
#extension GL_ARB_shader_stencil_export : require
diff --git a/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag b/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag
index 94368fb59..d33131d7c 100644
--- a/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag
+++ b/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 450
diff --git a/src/video_core/host_shaders/convert_depth_to_float.frag b/src/video_core/host_shaders/convert_depth_to_float.frag
index 624c58509..57f0ce5a6 100644
--- a/src/video_core/host_shaders/convert_depth_to_float.frag
+++ b/src/video_core/host_shaders/convert_depth_to_float.frag
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 450
diff --git a/src/video_core/host_shaders/convert_float_to_depth.frag b/src/video_core/host_shaders/convert_float_to_depth.frag
index d86c795f4..5d403b1ec 100644
--- a/src/video_core/host_shaders/convert_float_to_depth.frag
+++ b/src/video_core/host_shaders/convert_float_to_depth.frag
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 450
diff --git a/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag b/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag
new file mode 100644
index 000000000..31db7d426
--- /dev/null
+++ b/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#version 450
+
+layout(binding = 0) uniform sampler2D depth_tex;
+layout(binding = 1) uniform isampler2D stencil_tex;
+
+layout(location = 0) out vec4 color;
+
+void main() {
+ ivec2 coord = ivec2(gl_FragCoord.xy);
+ uint depth = uint(textureLod(depth_tex, coord, 0).r * (exp2(24.0) - 1.0f));
+ uint stencil = uint(textureLod(stencil_tex, coord, 0).r);
+
+ highp uint depth_val =
+ uint(textureLod(depth_tex, coord, 0).r * (exp2(32.0) - 1.0));
+ lowp uint stencil_val = textureLod(stencil_tex, coord, 0).r;
+ highp uvec4 components =
+ uvec4((uvec3(depth_val) >> uvec3(24u, 16u, 8u)) & 0x000000FFu, stencil_val);
+ color.rgba = vec4(components) / (exp2(8.0) - 1.0);
+}
diff --git a/src/video_core/host_shaders/fidelityfx_fsr.comp b/src/video_core/host_shaders/fidelityfx_fsr.comp
index 6b97f789d..f91b1aa9f 100644
--- a/src/video_core/host_shaders/fidelityfx_fsr.comp
+++ b/src/video_core/host_shaders/fidelityfx_fsr.comp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
//!#version 460 core
#extension GL_ARB_separate_shader_objects : enable
diff --git a/src/video_core/host_shaders/full_screen_triangle.vert b/src/video_core/host_shaders/full_screen_triangle.vert
index 452ad6502..2c976b19f 100644
--- a/src/video_core/host_shaders/full_screen_triangle.vert
+++ b/src/video_core/host_shaders/full_screen_triangle.vert
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 450
diff --git a/src/video_core/host_shaders/fxaa.frag b/src/video_core/host_shaders/fxaa.frag
index 02f4068d1..9bffc20d5 100644
--- a/src/video_core/host_shaders/fxaa.frag
+++ b/src/video_core/host_shaders/fxaa.frag
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
// Source code is adapted from
// https://www.geeks3d.com/20110405/fxaa-fast-approximate-anti-aliasing-demo-glsl-opengl-test-radeon-geforce/3/
diff --git a/src/video_core/host_shaders/fxaa.vert b/src/video_core/host_shaders/fxaa.vert
index ac20c04e9..c2717d90d 100644
--- a/src/video_core/host_shaders/fxaa.vert
+++ b/src/video_core/host_shaders/fxaa.vert
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 460
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..970203214
--- /dev/null
+++ b/src/video_core/host_shaders/opengl_convert_s8d24.comp
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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/host_shaders/opengl_copy_bc4.comp b/src/video_core/host_shaders/opengl_copy_bc4.comp
index 7b8e20fbe..8d3ae5a97 100644
--- a/src/video_core/host_shaders/opengl_copy_bc4.comp
+++ b/src/video_core/host_shaders/opengl_copy_bc4.comp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 430 core
#extension GL_ARB_gpu_shader_int64 : require
diff --git a/src/video_core/host_shaders/opengl_present.frag b/src/video_core/host_shaders/opengl_present.frag
index 84b818227..5fd7ad297 100644
--- a/src/video_core/host_shaders/opengl_present.frag
+++ b/src/video_core/host_shaders/opengl_present.frag
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 430 core
diff --git a/src/video_core/host_shaders/opengl_present.vert b/src/video_core/host_shaders/opengl_present.vert
index c3b5adbba..cc2f40163 100644
--- a/src/video_core/host_shaders/opengl_present.vert
+++ b/src/video_core/host_shaders/opengl_present.vert
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 430 core
diff --git a/src/video_core/host_shaders/opengl_present_scaleforce.frag b/src/video_core/host_shaders/opengl_present_scaleforce.frag
index 71ff9e1e3..a780373e3 100644
--- a/src/video_core/host_shaders/opengl_present_scaleforce.frag
+++ b/src/video_core/host_shaders/opengl_present_scaleforce.frag
@@ -1,24 +1,5 @@
-// MIT License
-//
-// Copyright (c) 2020 BreadFish64
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+// SPDX-FileCopyrightText: 2020 BreadFish64
+// SPDX-License-Identifier: MIT
// Adapted from https://github.com/BreadFish64/ScaleFish/tree/master/scaleforce
diff --git a/src/video_core/host_shaders/pitch_unswizzle.comp b/src/video_core/host_shaders/pitch_unswizzle.comp
index cb48ec170..7d23f594c 100644
--- a/src/video_core/host_shaders/pitch_unswizzle.comp
+++ b/src/video_core/host_shaders/pitch_unswizzle.comp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 430
diff --git a/src/video_core/host_shaders/present_bicubic.frag b/src/video_core/host_shaders/present_bicubic.frag
index 902b70c2b..c57dd2851 100644
--- a/src/video_core/host_shaders/present_bicubic.frag
+++ b/src/video_core/host_shaders/present_bicubic.frag
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 460 core
diff --git a/src/video_core/host_shaders/present_gaussian.frag b/src/video_core/host_shaders/present_gaussian.frag
index 66fed3238..5f54b71b6 100644
--- a/src/video_core/host_shaders/present_gaussian.frag
+++ b/src/video_core/host_shaders/present_gaussian.frag
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
// Code adapted from the following sources:
// - https://learnopengl.com/Advanced-Lighting/Bloom
diff --git a/src/video_core/host_shaders/source_shader.h.in b/src/video_core/host_shaders/source_shader.h.in
index 929dec39b..f189ee06b 100644
--- a/src/video_core/host_shaders/source_shader.h.in
+++ b/src/video_core/host_shaders/source_shader.h.in
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
#pragma once
#include <string_view>
diff --git a/src/video_core/host_shaders/vulkan_blit_color_float.frag b/src/video_core/host_shaders/vulkan_blit_color_float.frag
index 4a6aae410..c0c832296 100644
--- a/src/video_core/host_shaders/vulkan_blit_color_float.frag
+++ b/src/video_core/host_shaders/vulkan_blit_color_float.frag
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 450
diff --git a/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag b/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag
index 19bb23a5a..484b99773 100644
--- a/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag
+++ b/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 450
#extension GL_ARB_shader_stencil_export : require
diff --git a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.comp b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.comp
index 1c96a7905..00af13726 100644
--- a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.comp
+++ b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.comp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 460 core
#extension GL_GOOGLE_include_directive : enable
diff --git a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.comp b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.comp
index f4daff739..13d783fa8 100644
--- a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.comp
+++ b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.comp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 460 core
#extension GL_GOOGLE_include_directive : enable
diff --git a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.comp b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.comp
index 6b6796dd1..331549d96 100644
--- a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.comp
+++ b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.comp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 460 core
#extension GL_GOOGLE_include_directive : enable
diff --git a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.comp b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.comp
index f785eebf3..013ca0014 100644
--- a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.comp
+++ b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.comp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 460 core
#extension GL_GOOGLE_include_directive : enable
diff --git a/src/video_core/host_shaders/vulkan_present.frag b/src/video_core/host_shaders/vulkan_present.frag
index 0979ff3e6..97e098ced 100644
--- a/src/video_core/host_shaders/vulkan_present.frag
+++ b/src/video_core/host_shaders/vulkan_present.frag
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 460 core
diff --git a/src/video_core/host_shaders/vulkan_present.vert b/src/video_core/host_shaders/vulkan_present.vert
index 00b868958..89dc80468 100644
--- a/src/video_core/host_shaders/vulkan_present.vert
+++ b/src/video_core/host_shaders/vulkan_present.vert
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 460 core
diff --git a/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag b/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag
index 924c03060..3dc9c0df5 100644
--- a/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag
+++ b/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
#version 460
#extension GL_GOOGLE_include_directive : enable
diff --git a/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag b/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag
index a594b83ca..77ed07552 100644
--- a/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag
+++ b/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
#version 460
#extension GL_GOOGLE_include_directive : enable
diff --git a/src/video_core/host_shaders/vulkan_quad_indexed.comp b/src/video_core/host_shaders/vulkan_quad_indexed.comp
index 8655591d0..a412f30ff 100644
--- a/src/video_core/host_shaders/vulkan_quad_indexed.comp
+++ b/src/video_core/host_shaders/vulkan_quad_indexed.comp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 460 core
diff --git a/src/video_core/host_shaders/vulkan_uint8.comp b/src/video_core/host_shaders/vulkan_uint8.comp
index 872291670..5aa6abdc8 100644
--- a/src/video_core/host_shaders/vulkan_uint8.comp
+++ b/src/video_core/host_shaders/vulkan_uint8.comp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#version 460 core
#extension GL_EXT_shader_16bit_storage : require
diff --git a/src/video_core/macro/macro.cpp b/src/video_core/macro/macro.cpp
index 0aeda4ce8..f61d5998e 100644
--- a/src/video_core/macro/macro.cpp
+++ b/src/video_core/macro/macro.cpp
@@ -1,13 +1,17 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
+#include <fstream>
#include <optional>
+#include <span>
#include <boost/container_hash/hash.hpp>
+#include <fstream>
#include "common/assert.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
#include "common/settings.h"
#include "video_core/macro/macro.h"
#include "video_core/macro/macro_hle.h"
@@ -16,6 +20,23 @@
namespace Tegra {
+static void Dump(u64 hash, std::span<const u32> code) {
+ const auto base_dir{Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)};
+ const auto macro_dir{base_dir / "macros"};
+ if (!Common::FS::CreateDir(base_dir) || !Common::FS::CreateDir(macro_dir)) {
+ LOG_ERROR(Common_Filesystem, "Failed to create macro dump directories");
+ return;
+ }
+ const auto name{macro_dir / fmt::format("{:016x}.macro", hash)};
+ std::fstream macro_file(name, std::ios::out | std::ios::binary);
+ if (!macro_file) {
+ LOG_ERROR(Common_Filesystem, "Unable to open or create file at {}",
+ Common::FS::PathToUTF8String(name));
+ return;
+ }
+ macro_file.write(reinterpret_cast<const char*>(code.data()), code.size_bytes());
+}
+
MacroEngine::MacroEngine(Engines::Maxwell3D& maxwell3d)
: hle_macros{std::make_unique<Tegra::HLEMacro>(maxwell3d)} {}
@@ -25,6 +46,11 @@ void MacroEngine::AddCode(u32 method, u32 data) {
uploaded_macro_code[method].push_back(data);
}
+void MacroEngine::ClearCode(u32 method) {
+ macro_cache.erase(method);
+ uploaded_macro_code.erase(method);
+}
+
void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
auto compiled_macro = macro_cache.find(method);
if (compiled_macro != macro_cache.end()) {
@@ -46,7 +72,7 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
}
}
if (!mid_method.has_value()) {
- UNREACHABLE_MSG("Macro 0x{0:x} was not uploaded", method);
+ ASSERT_MSG(false, "Macro 0x{0:x} was not uploaded", method);
return;
}
}
@@ -55,6 +81,9 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
if (!mid_method.has_value()) {
cache_info.lle_program = Compile(macro_code->second);
cache_info.hash = boost::hash_value(macro_code->second);
+ if (Settings::values.dump_macros) {
+ Dump(cache_info.hash, macro_code->second);
+ }
} else {
const auto& macro_cached = uploaded_macro_code[mid_method.value()];
const auto rebased_method = method - mid_method.value();
@@ -64,6 +93,9 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
code.size() * sizeof(u32));
cache_info.hash = boost::hash_value(code);
cache_info.lle_program = Compile(code);
+ if (Settings::values.dump_macros) {
+ Dump(cache_info.hash, code);
+ }
}
if (auto hle_program = hle_macros->GetHLEProgram(cache_info.hash)) {
diff --git a/src/video_core/macro/macro.h b/src/video_core/macro/macro.h
index 7aaa49286..07d97ba39 100644
--- a/src/video_core/macro/macro.h
+++ b/src/video_core/macro/macro.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -118,6 +117,9 @@ public:
// Store the uploaded macro code to compile them when they're called.
void AddCode(u32 method, u32 data);
+ // Clear the code associated with a method.
+ void ClearCode(u32 method);
+
// Compiles the macro if its not in the cache, and executes the compiled macro
void Execute(u32 method, const std::vector<u32>& parameters);
diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp
index 900ad23c9..cabe8dcbf 100644
--- a/src/video_core/macro/macro_hle.cpp
+++ b/src/video_core/macro/macro_hle.cpp
@@ -1,9 +1,10 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <vector>
+#include "common/scope_exit.h"
+#include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/macro/macro.h"
#include "video_core/macro/macro_hle.h"
@@ -59,6 +60,7 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
maxwell3d.regs.index_array.first = parameters[3];
maxwell3d.regs.reg_array[0x446] = element_base; // vertex id base?
maxwell3d.regs.index_array.count = parameters[1];
+ maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
maxwell3d.regs.vb_element_base = element_base;
maxwell3d.regs.vb_base_instance = base_instance;
maxwell3d.mme_draw.instance_count = instance_count;
@@ -81,10 +83,67 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
}
-constexpr std::array<std::pair<u64, HLEFunction>, 3> hle_funcs{{
+// Multidraw Indirect
+void HLE_3F5E74B9C9A50164(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters) {
+ SCOPE_EXIT({
+ // Clean everything.
+ maxwell3d.regs.reg_array[0x446] = 0x0; // vertex id base?
+ maxwell3d.regs.index_array.count = 0;
+ maxwell3d.regs.vb_element_base = 0x0;
+ maxwell3d.regs.vb_base_instance = 0x0;
+ maxwell3d.mme_draw.instance_count = 0;
+ maxwell3d.CallMethodFromMME(0x8e3, 0x640);
+ maxwell3d.CallMethodFromMME(0x8e4, 0x0);
+ maxwell3d.CallMethodFromMME(0x8e5, 0x0);
+ maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
+ maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
+ });
+ const u32 start_indirect = parameters[0];
+ const u32 end_indirect = parameters[1];
+ if (start_indirect >= end_indirect) {
+ // Nothing to do.
+ return;
+ }
+ const auto topology =
+ static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[2]);
+ maxwell3d.regs.draw.topology.Assign(topology);
+ const u32 padding = parameters[3];
+ const std::size_t max_draws = parameters[4];
+
+ const u32 indirect_words = 5 + padding;
+ const std::size_t first_draw = start_indirect;
+ const std::size_t effective_draws = end_indirect - start_indirect;
+ const std::size_t last_draw = start_indirect + std::min(effective_draws, max_draws);
+
+ for (std::size_t index = first_draw; index < last_draw; index++) {
+ const std::size_t base = index * indirect_words + 5;
+ const u32 num_vertices = parameters[base];
+ const u32 instance_count = parameters[base + 1];
+ const u32 first_index = parameters[base + 2];
+ const u32 base_vertex = parameters[base + 3];
+ const u32 base_instance = parameters[base + 4];
+ maxwell3d.regs.index_array.first = first_index;
+ maxwell3d.regs.reg_array[0x446] = base_vertex;
+ maxwell3d.regs.index_array.count = num_vertices;
+ maxwell3d.regs.vb_element_base = base_vertex;
+ maxwell3d.regs.vb_base_instance = base_instance;
+ maxwell3d.mme_draw.instance_count = instance_count;
+ maxwell3d.CallMethodFromMME(0x8e3, 0x640);
+ maxwell3d.CallMethodFromMME(0x8e4, base_vertex);
+ maxwell3d.CallMethodFromMME(0x8e5, base_instance);
+ maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
+ if (maxwell3d.ShouldExecute()) {
+ maxwell3d.Rasterizer().Draw(true, true);
+ }
+ maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
+ }
+}
+
+constexpr std::array<std::pair<u64, HLEFunction>, 4> hle_funcs{{
{0x771BB18C62444DA0, &HLE_771BB18C62444DA0},
{0x0D61FC9FAAC9FCAD, &HLE_0D61FC9FAAC9FCAD},
{0x0217920100488FF7, &HLE_0217920100488FF7},
+ {0x3F5E74B9C9A50164, &HLE_3F5E74B9C9A50164},
}};
class HLEMacroImpl final : public CachedMacro {
@@ -100,6 +159,7 @@ private:
Engines::Maxwell3D& maxwell3d;
HLEFunction func;
};
+
} // Anonymous namespace
HLEMacro::HLEMacro(Engines::Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {}
diff --git a/src/video_core/macro/macro_hle.h b/src/video_core/macro/macro_hle.h
index b86ba84a1..625332c9d 100644
--- a/src/video_core/macro/macro_hle.h
+++ b/src/video_core/macro/macro_hle.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/macro/macro_interpreter.cpp b/src/video_core/macro/macro_interpreter.cpp
index fba755448..f670b1bca 100644
--- a/src/video_core/macro/macro_interpreter.cpp
+++ b/src/video_core/macro/macro_interpreter.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <optional>
@@ -309,7 +308,6 @@ bool MacroInterpreterImpl::EvaluateBranchCondition(Macro::BranchCondition cond,
return value != 0;
}
UNREACHABLE();
- return true;
}
Macro::Opcode MacroInterpreterImpl::GetOpcode() const {
diff --git a/src/video_core/macro/macro_interpreter.h b/src/video_core/macro/macro_interpreter.h
index 8a9648e46..f5eeb0b76 100644
--- a/src/video_core/macro/macro_interpreter.h
+++ b/src/video_core/macro/macro_interpreter.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/macro/macro_jit_x64.cpp b/src/video_core/macro/macro_jit_x64.cpp
index 47b28ad16..a302a9603 100644
--- a/src/video_core/macro/macro_jit_x64.cpp
+++ b/src/video_core/macro/macro_jit_x64.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <bitset>
@@ -24,7 +23,8 @@ MICROPROFILE_DEFINE(MacroJitExecute, "GPU", "Execute macro JIT", MP_RGB(255, 255
namespace Tegra {
namespace {
constexpr Xbyak::Reg64 STATE = Xbyak::util::rbx;
-constexpr Xbyak::Reg32 RESULT = Xbyak::util::ebp;
+constexpr Xbyak::Reg32 RESULT = Xbyak::util::r10d;
+constexpr Xbyak::Reg64 MAX_PARAMETER = Xbyak::util::r11;
constexpr Xbyak::Reg64 PARAMETERS = Xbyak::util::r12;
constexpr Xbyak::Reg32 METHOD_ADDRESS = Xbyak::util::r14d;
constexpr Xbyak::Reg64 BRANCH_HOLDER = Xbyak::util::r15;
@@ -32,6 +32,7 @@ constexpr Xbyak::Reg64 BRANCH_HOLDER = Xbyak::util::r15;
constexpr std::bitset<32> PERSISTENT_REGISTERS = Common::X64::BuildRegSet({
STATE,
RESULT,
+ MAX_PARAMETER,
PARAMETERS,
METHOD_ADDRESS,
BRANCH_HOLDER,
@@ -81,7 +82,7 @@ private:
u32 carry_flag{};
};
static_assert(offsetof(JITState, maxwell3d) == 0, "Maxwell3D is not at 0x0");
- using ProgramType = void (*)(JITState*, const u32*);
+ using ProgramType = void (*)(JITState*, const u32*, const u32*);
struct OptimizerState {
bool can_skip_carry{};
@@ -113,7 +114,7 @@ void MacroJITx64Impl::Execute(const std::vector<u32>& parameters, u32 method) {
JITState state{};
state.maxwell3d = &maxwell3d;
state.registers = {};
- program(&state, parameters.data());
+ program(&state, parameters.data(), parameters.data() + parameters.size());
}
void MacroJITx64Impl::Compile_ALU(Macro::Opcode opcode) {
@@ -278,28 +279,13 @@ void MacroJITx64Impl::Compile_ExtractInsert(Macro::Opcode opcode) {
auto dst = Compile_GetRegister(opcode.src_a, RESULT);
auto src = Compile_GetRegister(opcode.src_b, eax);
- if (opcode.bf_src_bit != 0 && opcode.bf_src_bit != 31) {
- shr(src, opcode.bf_src_bit);
- } else if (opcode.bf_src_bit == 31) {
- xor_(src, src);
- }
- // Don't bother masking the whole register since we're using a 32 bit register
- if (opcode.bf_size != 31 && opcode.bf_size != 0) {
- and_(src, opcode.GetBitfieldMask());
- } else if (opcode.bf_size == 0) {
- xor_(src, src);
- }
- if (opcode.bf_dst_bit != 31 && opcode.bf_dst_bit != 0) {
- shl(src, opcode.bf_dst_bit);
- } else if (opcode.bf_dst_bit == 31) {
- xor_(src, src);
- }
-
const u32 mask = ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit);
- if (mask != 0xffffffff) {
- and_(dst, mask);
- }
+ and_(dst, mask);
+ shr(src, opcode.bf_src_bit);
+ and_(src, opcode.GetBitfieldMask());
+ shl(src, opcode.bf_dst_bit);
or_(dst, src);
+
Compile_ProcessResult(opcode.result_operation, opcode.dst);
}
@@ -308,17 +294,9 @@ void MacroJITx64Impl::Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode) {
const auto src = Compile_GetRegister(opcode.src_b, RESULT);
shr(src, dst.cvt8());
- if (opcode.bf_size != 0 && opcode.bf_size != 31) {
- and_(src, opcode.GetBitfieldMask());
- } else if (opcode.bf_size == 0) {
- xor_(src, src);
- }
+ and_(src, opcode.GetBitfieldMask());
+ shl(src, opcode.bf_dst_bit);
- if (opcode.bf_dst_bit != 0 && opcode.bf_dst_bit != 31) {
- shl(src, opcode.bf_dst_bit);
- } else if (opcode.bf_dst_bit == 31) {
- xor_(src, src);
- }
Compile_ProcessResult(opcode.result_operation, opcode.dst);
}
@@ -326,13 +304,8 @@ void MacroJITx64Impl::Compile_ExtractShiftLeftRegister(Macro::Opcode opcode) {
const auto dst = Compile_GetRegister(opcode.src_a, ecx);
const auto src = Compile_GetRegister(opcode.src_b, RESULT);
- if (opcode.bf_src_bit != 0) {
- shr(src, opcode.bf_src_bit);
- }
-
- if (opcode.bf_size != 31) {
- and_(src, opcode.GetBitfieldMask());
- }
+ shr(src, opcode.bf_src_bit);
+ and_(src, opcode.GetBitfieldMask());
shl(src, dst.cvt8());
Compile_ProcessResult(opcode.result_operation, opcode.dst);
@@ -410,7 +383,7 @@ void MacroJITx64Impl::Compile_Branch(Macro::Opcode opcode) {
Xbyak::Label end;
auto value = Compile_GetRegister(opcode.src_a, eax);
- test(value, value);
+ cmp(value, 0); // test(value, value);
if (optimizer.has_delayed_pc) {
switch (opcode.branch_condition) {
case Macro::BranchCondition::Zero:
@@ -428,17 +401,11 @@ void MacroJITx64Impl::Compile_Branch(Macro::Opcode opcode) {
Xbyak::Label handle_post_exit{};
Xbyak::Label skip{};
jmp(skip, T_NEAR);
- if (opcode.is_exit) {
- L(handle_post_exit);
- // Execute 1 instruction
- mov(BRANCH_HOLDER, end_of_code);
- // Jump to next instruction to skip delay slot check
- jmp(labels[jump_address], T_NEAR);
- } else {
- L(handle_post_exit);
- xor_(BRANCH_HOLDER, BRANCH_HOLDER);
- jmp(labels[jump_address], T_NEAR);
- }
+
+ L(handle_post_exit);
+ xor_(BRANCH_HOLDER, BRANCH_HOLDER);
+ jmp(labels[jump_address], T_NEAR);
+
L(skip);
mov(BRANCH_HOLDER, handle_post_exit);
jmp(delay_skip[pc], T_NEAR);
@@ -489,6 +456,7 @@ void MacroJITx64Impl::Compile() {
// JIT state
mov(STATE, Common::X64::ABI_PARAM1);
mov(PARAMETERS, Common::X64::ABI_PARAM2);
+ mov(MAX_PARAMETER, Common::X64::ABI_PARAM3);
xor_(RESULT, RESULT);
xor_(METHOD_ADDRESS, METHOD_ADDRESS);
xor_(BRANCH_HOLDER, BRANCH_HOLDER);
@@ -599,7 +567,22 @@ bool MacroJITx64Impl::Compile_NextInstruction() {
return true;
}
+static void WarnInvalidParameter(uintptr_t parameter, uintptr_t max_parameter) {
+ LOG_CRITICAL(HW_GPU,
+ "Macro JIT: invalid parameter access 0x{:x} (0x{:x} is the last parameter)",
+ parameter, max_parameter - sizeof(u32));
+}
+
Xbyak::Reg32 MacroJITx64Impl::Compile_FetchParameter() {
+ Xbyak::Label parameter_ok{};
+ cmp(PARAMETERS, MAX_PARAMETER);
+ jb(parameter_ok, T_NEAR);
+ Common::X64::ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+ mov(Common::X64::ABI_PARAM1, PARAMETERS);
+ mov(Common::X64::ABI_PARAM2, MAX_PARAMETER);
+ Common::X64::CallFarFunction(*this, &WarnInvalidParameter);
+ Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+ L(parameter_ok);
mov(eax, dword[PARAMETERS]);
add(PARAMETERS, sizeof(u32));
return eax;
diff --git a/src/video_core/macro/macro_jit_x64.h b/src/video_core/macro/macro_jit_x64.h
index 773b037ae..99ee1b9e6 100644
--- a/src/video_core/macro/macro_jit_x64.h
+++ b/src/video_core/macro/macro_jit_x64.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 4ff3fa268..cca401c74 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
@@ -8,182 +7,208 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
+#include "core/device_memory.h"
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/memory.h"
-#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
namespace Tegra {
-MemoryManager::MemoryManager(Core::System& system_)
- : system{system_}, page_table(page_table_size) {}
+std::atomic<size_t> MemoryManager::unique_identifier_generator{};
+
+MemoryManager::MemoryManager(Core::System& system_, u64 address_space_bits_, u64 big_page_bits_,
+ u64 page_bits_)
+ : system{system_}, memory{system.Memory()}, device_memory{system.DeviceMemory()},
+ address_space_bits{address_space_bits_}, page_bits{page_bits_}, big_page_bits{big_page_bits_},
+ entries{}, big_entries{}, page_table{address_space_bits, address_space_bits + page_bits - 38,
+ page_bits != big_page_bits ? page_bits : 0},
+ unique_identifier{unique_identifier_generator.fetch_add(1, std::memory_order_acq_rel)} {
+ address_space_size = 1ULL << address_space_bits;
+ page_size = 1ULL << page_bits;
+ page_mask = page_size - 1ULL;
+ big_page_size = 1ULL << big_page_bits;
+ big_page_mask = big_page_size - 1ULL;
+ const u64 page_table_bits = address_space_bits - page_bits;
+ const u64 big_page_table_bits = address_space_bits - big_page_bits;
+ const u64 page_table_size = 1ULL << page_table_bits;
+ const u64 big_page_table_size = 1ULL << big_page_table_bits;
+ page_table_mask = page_table_size - 1;
+ big_page_table_mask = big_page_table_size - 1;
+
+ big_entries.resize(big_page_table_size / 32, 0);
+ big_page_table_cpu.resize(big_page_table_size);
+ big_page_continous.resize(big_page_table_size / continous_bits, 0);
+ entries.resize(page_table_size / 32, 0);
+}
MemoryManager::~MemoryManager() = default;
-void MemoryManager::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
- rasterizer = rasterizer_;
-}
-
-GPUVAddr MemoryManager::UpdateRange(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size) {
- u64 remaining_size{size};
- for (u64 offset{}; offset < size; offset += page_size) {
- if (remaining_size < page_size) {
- SetPageEntry(gpu_addr + offset, page_entry + offset, remaining_size);
- } else {
- SetPageEntry(gpu_addr + offset, page_entry + offset);
- }
- remaining_size -= page_size;
+template <bool is_big_page>
+MemoryManager::EntryType MemoryManager::GetEntry(size_t position) const {
+ if constexpr (is_big_page) {
+ position = position >> big_page_bits;
+ const u64 entry_mask = big_entries[position / 32];
+ const size_t sub_index = position % 32;
+ return static_cast<EntryType>((entry_mask >> (2 * sub_index)) & 0x03ULL);
+ } else {
+ position = position >> page_bits;
+ const u64 entry_mask = entries[position / 32];
+ const size_t sub_index = position % 32;
+ return static_cast<EntryType>((entry_mask >> (2 * sub_index)) & 0x03ULL);
}
- return gpu_addr;
}
-GPUVAddr MemoryManager::Map(VAddr cpu_addr, GPUVAddr gpu_addr, std::size_t size) {
- const auto it = std::ranges::lower_bound(map_ranges, gpu_addr, {}, &MapRange::first);
- if (it != map_ranges.end() && it->first == gpu_addr) {
- it->second = size;
+template <bool is_big_page>
+void MemoryManager::SetEntry(size_t position, MemoryManager::EntryType entry) {
+ if constexpr (is_big_page) {
+ position = position >> big_page_bits;
+ const u64 entry_mask = big_entries[position / 32];
+ const size_t sub_index = position % 32;
+ big_entries[position / 32] =
+ (~(3ULL << sub_index * 2) & entry_mask) | (static_cast<u64>(entry) << sub_index * 2);
} else {
- map_ranges.insert(it, MapRange{gpu_addr, size});
+ position = position >> page_bits;
+ const u64 entry_mask = entries[position / 32];
+ const size_t sub_index = position % 32;
+ entries[position / 32] =
+ (~(3ULL << sub_index * 2) & entry_mask) | (static_cast<u64>(entry) << sub_index * 2);
}
- return UpdateRange(gpu_addr, cpu_addr, size);
}
-GPUVAddr MemoryManager::MapAllocate(VAddr cpu_addr, std::size_t size, std::size_t align) {
- return Map(cpu_addr, *FindFreeRange(size, align), size);
+inline bool MemoryManager::IsBigPageContinous(size_t big_page_index) const {
+ const u64 entry_mask = big_page_continous[big_page_index / continous_bits];
+ const size_t sub_index = big_page_index % continous_bits;
+ return ((entry_mask >> sub_index) & 0x1ULL) != 0;
}
-GPUVAddr MemoryManager::MapAllocate32(VAddr cpu_addr, std::size_t size) {
- const std::optional<GPUVAddr> gpu_addr = FindFreeRange(size, 1, true);
- ASSERT(gpu_addr);
- return Map(cpu_addr, *gpu_addr, size);
+inline void MemoryManager::SetBigPageContinous(size_t big_page_index, bool value) {
+ const u64 continous_mask = big_page_continous[big_page_index / continous_bits];
+ const size_t sub_index = big_page_index % continous_bits;
+ big_page_continous[big_page_index / continous_bits] =
+ (~(1ULL << sub_index) & continous_mask) | (value ? 1ULL << sub_index : 0);
}
-void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) {
- if (size == 0) {
- return;
- }
- const auto it = std::ranges::lower_bound(map_ranges, gpu_addr, {}, &MapRange::first);
- if (it != map_ranges.end()) {
- ASSERT(it->first == gpu_addr);
- map_ranges.erase(it);
- } else {
- UNREACHABLE_MSG("Unmapping non-existent GPU address=0x{:x}", gpu_addr);
- }
- const auto submapped_ranges = GetSubmappedRange(gpu_addr, size);
-
- for (const auto& [map_addr, map_size] : submapped_ranges) {
- // Flush and invalidate through the GPU interface, to be asynchronous if possible.
- const std::optional<VAddr> cpu_addr = GpuToCpuAddress(map_addr);
- ASSERT(cpu_addr);
-
- rasterizer->UnmapMemory(*cpu_addr, map_size);
+template <MemoryManager::EntryType entry_type>
+GPUVAddr MemoryManager::PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cpu_addr,
+ size_t size) {
+ u64 remaining_size{size};
+ if constexpr (entry_type == EntryType::Mapped) {
+ page_table.ReserveRange(gpu_addr, size);
}
-
- UpdateRange(gpu_addr, PageEntry::State::Unmapped, size);
-}
-
-std::optional<GPUVAddr> MemoryManager::AllocateFixed(GPUVAddr gpu_addr, std::size_t size) {
for (u64 offset{}; offset < size; offset += page_size) {
- if (!GetPageEntry(gpu_addr + offset).IsUnmapped()) {
- return std::nullopt;
+ const GPUVAddr current_gpu_addr = gpu_addr + offset;
+ [[maybe_unused]] const auto current_entry_type = GetEntry<false>(current_gpu_addr);
+ SetEntry<false>(current_gpu_addr, entry_type);
+ if (current_entry_type != entry_type) {
+ rasterizer->ModifyGPUMemory(unique_identifier, gpu_addr, page_size);
+ }
+ if constexpr (entry_type == EntryType::Mapped) {
+ const VAddr current_cpu_addr = cpu_addr + offset;
+ const auto index = PageEntryIndex<false>(current_gpu_addr);
+ const u32 sub_value = static_cast<u32>(current_cpu_addr >> cpu_page_bits);
+ page_table[index] = sub_value;
}
+ remaining_size -= page_size;
}
-
- return UpdateRange(gpu_addr, PageEntry::State::Allocated, size);
-}
-
-GPUVAddr MemoryManager::Allocate(std::size_t size, std::size_t align) {
- return *AllocateFixed(*FindFreeRange(size, align), size);
+ return gpu_addr;
}
-void MemoryManager::TryLockPage(PageEntry page_entry, std::size_t size) {
- if (!page_entry.IsValid()) {
- return;
+template <MemoryManager::EntryType entry_type>
+GPUVAddr MemoryManager::BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cpu_addr,
+ size_t size) {
+ u64 remaining_size{size};
+ for (u64 offset{}; offset < size; offset += big_page_size) {
+ const GPUVAddr current_gpu_addr = gpu_addr + offset;
+ [[maybe_unused]] const auto current_entry_type = GetEntry<true>(current_gpu_addr);
+ SetEntry<true>(current_gpu_addr, entry_type);
+ if (current_entry_type != entry_type) {
+ rasterizer->ModifyGPUMemory(unique_identifier, gpu_addr, big_page_size);
+ }
+ if constexpr (entry_type == EntryType::Mapped) {
+ const VAddr current_cpu_addr = cpu_addr + offset;
+ const auto index = PageEntryIndex<true>(current_gpu_addr);
+ const u32 sub_value = static_cast<u32>(current_cpu_addr >> cpu_page_bits);
+ big_page_table_cpu[index] = sub_value;
+ const bool is_continous = ([&] {
+ uintptr_t base_ptr{
+ reinterpret_cast<uintptr_t>(memory.GetPointerSilent(current_cpu_addr))};
+ if (base_ptr == 0) {
+ return false;
+ }
+ for (VAddr start_cpu = current_cpu_addr + page_size;
+ start_cpu < current_cpu_addr + big_page_size; start_cpu += page_size) {
+ base_ptr += page_size;
+ auto next_ptr = reinterpret_cast<uintptr_t>(memory.GetPointerSilent(start_cpu));
+ if (next_ptr == 0 || base_ptr != next_ptr) {
+ return false;
+ }
+ }
+ return true;
+ })();
+ SetBigPageContinous(index, is_continous);
+ }
+ remaining_size -= big_page_size;
}
-
- ASSERT(system.CurrentProcess()
- ->PageTable()
- .LockForDeviceAddressSpace(page_entry.ToAddress(), size)
- .IsSuccess());
+ return gpu_addr;
}
-void MemoryManager::TryUnlockPage(PageEntry page_entry, std::size_t size) {
- if (!page_entry.IsValid()) {
- return;
- }
-
- ASSERT(system.CurrentProcess()
- ->PageTable()
- .UnlockForDeviceAddressSpace(page_entry.ToAddress(), size)
- .IsSuccess());
+void MemoryManager::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
+ rasterizer = rasterizer_;
}
-PageEntry MemoryManager::GetPageEntry(GPUVAddr gpu_addr) const {
- return page_table[PageEntryIndex(gpu_addr)];
+GPUVAddr MemoryManager::Map(GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size,
+ bool is_big_pages) {
+ if (is_big_pages) [[likely]] {
+ return BigPageTableOp<EntryType::Mapped>(gpu_addr, cpu_addr, size);
+ }
+ return PageTableOp<EntryType::Mapped>(gpu_addr, cpu_addr, size);
}
-void MemoryManager::SetPageEntry(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size) {
- // TODO(bunnei): We should lock/unlock device regions. This currently causes issues due to
- // improper tracking, but should be fixed in the future.
-
- //// Unlock the old page
- // TryUnlockPage(page_table[PageEntryIndex(gpu_addr)], size);
-
- //// Lock the new page
- // TryLockPage(page_entry, size);
- auto& current_page = page_table[PageEntryIndex(gpu_addr)];
-
- if ((!current_page.IsValid() && page_entry.IsValid()) ||
- current_page.ToAddress() != page_entry.ToAddress()) {
- rasterizer->ModifyGPUMemory(gpu_addr, size);
+GPUVAddr MemoryManager::MapSparse(GPUVAddr gpu_addr, std::size_t size, bool is_big_pages) {
+ if (is_big_pages) [[likely]] {
+ return BigPageTableOp<EntryType::Reserved>(gpu_addr, 0, size);
}
-
- current_page = page_entry;
+ return PageTableOp<EntryType::Reserved>(gpu_addr, 0, size);
}
-std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size_t align,
- bool start_32bit_address) const {
- if (!align) {
- align = page_size;
- } else {
- align = Common::AlignUp(align, page_size);
+void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) {
+ if (size == 0) {
+ return;
}
+ const auto submapped_ranges = GetSubmappedRange(gpu_addr, size);
- u64 available_size{};
- GPUVAddr gpu_addr{start_32bit_address ? address_space_start_low : address_space_start};
- while (gpu_addr + available_size < address_space_size) {
- if (GetPageEntry(gpu_addr + available_size).IsUnmapped()) {
- available_size += page_size;
-
- if (available_size >= size) {
- return gpu_addr;
- }
- } else {
- gpu_addr += available_size + page_size;
- available_size = 0;
+ for (const auto& [map_addr, map_size] : submapped_ranges) {
+ // Flush and invalidate through the GPU interface, to be asynchronous if possible.
+ const std::optional<VAddr> cpu_addr = GpuToCpuAddress(map_addr);
+ ASSERT(cpu_addr);
- const auto remainder{gpu_addr % align};
- if (remainder) {
- gpu_addr = (gpu_addr - remainder) + align;
- }
- }
+ rasterizer->UnmapMemory(*cpu_addr, map_size);
}
- return std::nullopt;
+ BigPageTableOp<EntryType::Free>(gpu_addr, 0, size);
+ PageTableOp<EntryType::Free>(gpu_addr, 0, size);
}
std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const {
- if (gpu_addr == 0) {
+ if (!IsWithinGPUAddressRange(gpu_addr)) [[unlikely]] {
return std::nullopt;
}
- const auto page_entry{GetPageEntry(gpu_addr)};
- if (!page_entry.IsValid()) {
- return std::nullopt;
+ if (GetEntry<true>(gpu_addr) != EntryType::Mapped) [[unlikely]] {
+ if (GetEntry<false>(gpu_addr) != EntryType::Mapped) {
+ return std::nullopt;
+ }
+
+ const VAddr cpu_addr_base = static_cast<VAddr>(page_table[PageEntryIndex<false>(gpu_addr)])
+ << cpu_page_bits;
+ return cpu_addr_base + (gpu_addr & page_mask);
}
- return page_entry.ToAddress() + (gpu_addr & page_mask);
+ const VAddr cpu_addr_base =
+ static_cast<VAddr>(big_page_table_cpu[PageEntryIndex<true>(gpu_addr)]) << cpu_page_bits;
+ return cpu_addr_base + (gpu_addr & big_page_mask);
}
std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr addr, std::size_t size) const {
@@ -191,7 +216,7 @@ std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr addr, std::size_t s
const size_t page_last{(addr + size + page_size - 1) >> page_bits};
while (page_index < page_last) {
const auto page_addr{GpuToCpuAddress(page_index << page_bits)};
- if (page_addr && *page_addr != 0) {
+ if (page_addr) {
return page_addr;
}
++page_index;
@@ -208,7 +233,7 @@ T MemoryManager::Read(GPUVAddr addr) const {
return value;
}
- UNREACHABLE();
+ ASSERT(false);
return {};
}
@@ -221,7 +246,7 @@ void MemoryManager::Write(GPUVAddr addr, T data) {
return;
}
- UNREACHABLE();
+ ASSERT(false);
}
template u8 MemoryManager::Read<u8>(GPUVAddr addr) const;
@@ -234,126 +259,298 @@ template void MemoryManager::Write<u32>(GPUVAddr addr, u32 data);
template void MemoryManager::Write<u64>(GPUVAddr addr, u64 data);
u8* MemoryManager::GetPointer(GPUVAddr gpu_addr) {
- if (!GetPageEntry(gpu_addr).IsValid()) {
- return {};
- }
-
const auto address{GpuToCpuAddress(gpu_addr)};
if (!address) {
return {};
}
- return system.Memory().GetPointer(*address);
+ return memory.GetPointer(*address);
}
const u8* MemoryManager::GetPointer(GPUVAddr gpu_addr) const {
- if (!GetPageEntry(gpu_addr).IsValid()) {
- return {};
- }
-
const auto address{GpuToCpuAddress(gpu_addr)};
if (!address) {
return {};
}
- return system.Memory().GetPointer(*address);
-}
-
-size_t MemoryManager::BytesToMapEnd(GPUVAddr gpu_addr) const noexcept {
- auto it = std::ranges::upper_bound(map_ranges, gpu_addr, {}, &MapRange::first);
- --it;
- return it->second - (gpu_addr - it->first);
-}
-
-void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size,
- bool is_safe) const {
+ return memory.GetPointer(*address);
+}
+
+#ifdef _MSC_VER // no need for gcc / clang but msvc's compiler is more conservative with inlining.
+#pragma inline_recursion(on)
+#endif
+
+template <bool is_big_pages, typename FuncMapped, typename FuncReserved, typename FuncUnmapped>
+inline void MemoryManager::MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size,
+ FuncMapped&& func_mapped, FuncReserved&& func_reserved,
+ FuncUnmapped&& func_unmapped) const {
+ static constexpr bool BOOL_BREAK_MAPPED = std::is_same_v<FuncMapped, bool>;
+ static constexpr bool BOOL_BREAK_RESERVED = std::is_same_v<FuncReserved, bool>;
+ static constexpr bool BOOL_BREAK_UNMAPPED = std::is_same_v<FuncUnmapped, bool>;
+ u64 used_page_size;
+ u64 used_page_mask;
+ u64 used_page_bits;
+ if constexpr (is_big_pages) {
+ used_page_size = big_page_size;
+ used_page_mask = big_page_mask;
+ used_page_bits = big_page_bits;
+ } else {
+ used_page_size = page_size;
+ used_page_mask = page_mask;
+ used_page_bits = page_bits;
+ }
std::size_t remaining_size{size};
- std::size_t page_index{gpu_src_addr >> page_bits};
- std::size_t page_offset{gpu_src_addr & page_mask};
+ std::size_t page_index{gpu_src_addr >> used_page_bits};
+ std::size_t page_offset{gpu_src_addr & used_page_mask};
+ GPUVAddr current_address = gpu_src_addr;
while (remaining_size > 0) {
const std::size_t copy_amount{
- std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
- const auto page_addr{GpuToCpuAddress(page_index << page_bits)};
- if (page_addr && *page_addr != 0) {
- const auto src_addr{*page_addr + page_offset};
- if (is_safe) {
- // Flush must happen on the rasterizer interface, such that memory is always
- // synchronous when it is read (even when in asynchronous GPU mode).
- // Fixes Dead Cells title menu.
- rasterizer->FlushRegion(src_addr, copy_amount);
+ std::min(static_cast<std::size_t>(used_page_size) - page_offset, remaining_size)};
+ auto entry = GetEntry<is_big_pages>(current_address);
+ if (entry == EntryType::Mapped) [[likely]] {
+ if constexpr (BOOL_BREAK_MAPPED) {
+ if (func_mapped(page_index, page_offset, copy_amount)) {
+ return;
+ }
+ } else {
+ func_mapped(page_index, page_offset, copy_amount);
}
- system.Memory().ReadBlockUnsafe(src_addr, dest_buffer, copy_amount);
- } else {
- std::memset(dest_buffer, 0, copy_amount);
- }
+ } else if (entry == EntryType::Reserved) {
+ if constexpr (BOOL_BREAK_RESERVED) {
+ if (func_reserved(page_index, page_offset, copy_amount)) {
+ return;
+ }
+ } else {
+ func_reserved(page_index, page_offset, copy_amount);
+ }
+
+ } else [[unlikely]] {
+ if constexpr (BOOL_BREAK_UNMAPPED) {
+ if (func_unmapped(page_index, page_offset, copy_amount)) {
+ return;
+ }
+ } else {
+ func_unmapped(page_index, page_offset, copy_amount);
+ }
+ }
page_index++;
page_offset = 0;
- dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
remaining_size -= copy_amount;
+ current_address += copy_amount;
}
}
+template <bool is_safe>
+void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer,
+ std::size_t size) const {
+ auto set_to_zero = [&]([[maybe_unused]] std::size_t page_index,
+ [[maybe_unused]] std::size_t offset, std::size_t copy_amount) {
+ std::memset(dest_buffer, 0, copy_amount);
+ dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
+ };
+ auto mapped_normal = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(page_table[page_index]) << cpu_page_bits) + offset;
+ if constexpr (is_safe) {
+ rasterizer->FlushRegion(cpu_addr_base, copy_amount);
+ }
+ u8* physical = memory.GetPointer(cpu_addr_base);
+ std::memcpy(dest_buffer, physical, copy_amount);
+ dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
+ };
+ auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(big_page_table_cpu[page_index]) << cpu_page_bits) + offset;
+ if constexpr (is_safe) {
+ rasterizer->FlushRegion(cpu_addr_base, copy_amount);
+ }
+ if (!IsBigPageContinous(page_index)) [[unlikely]] {
+ memory.ReadBlockUnsafe(cpu_addr_base, dest_buffer, copy_amount);
+ } else {
+ u8* physical = memory.GetPointer(cpu_addr_base);
+ std::memcpy(dest_buffer, physical, copy_amount);
+ }
+ dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
+ };
+ auto read_short_pages = [&](std::size_t page_index, std::size_t offset,
+ std::size_t copy_amount) {
+ GPUVAddr base = (page_index << big_page_bits) + offset;
+ MemoryOperation<false>(base, copy_amount, mapped_normal, set_to_zero, set_to_zero);
+ };
+ MemoryOperation<true>(gpu_src_addr, size, mapped_big, set_to_zero, read_short_pages);
+}
+
void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size) const {
- ReadBlockImpl(gpu_src_addr, dest_buffer, size, true);
+ ReadBlockImpl<true>(gpu_src_addr, dest_buffer, size);
}
void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer,
const std::size_t size) const {
- ReadBlockImpl(gpu_src_addr, dest_buffer, size, false);
+ ReadBlockImpl<false>(gpu_src_addr, dest_buffer, size);
}
-void MemoryManager::WriteBlockImpl(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size,
- bool is_safe) {
- std::size_t remaining_size{size};
- std::size_t page_index{gpu_dest_addr >> page_bits};
- std::size_t page_offset{gpu_dest_addr & page_mask};
-
- while (remaining_size > 0) {
- const std::size_t copy_amount{
- std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
- const auto page_addr{GpuToCpuAddress(page_index << page_bits)};
- if (page_addr && *page_addr != 0) {
- const auto dest_addr{*page_addr + page_offset};
-
- if (is_safe) {
- // Invalidate must happen on the rasterizer interface, such that memory is always
- // synchronous when it is written (even when in asynchronous GPU mode).
- rasterizer->InvalidateRegion(dest_addr, copy_amount);
- }
- system.Memory().WriteBlockUnsafe(dest_addr, src_buffer, copy_amount);
+template <bool is_safe>
+void MemoryManager::WriteBlockImpl(GPUVAddr gpu_dest_addr, const void* src_buffer,
+ std::size_t size) {
+ auto just_advance = [&]([[maybe_unused]] std::size_t page_index,
+ [[maybe_unused]] std::size_t offset, std::size_t copy_amount) {
+ src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
+ };
+ auto mapped_normal = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(page_table[page_index]) << cpu_page_bits) + offset;
+ if constexpr (is_safe) {
+ rasterizer->InvalidateRegion(cpu_addr_base, copy_amount);
}
-
- page_index++;
- page_offset = 0;
+ u8* physical = memory.GetPointer(cpu_addr_base);
+ std::memcpy(physical, src_buffer, copy_amount);
src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
- remaining_size -= copy_amount;
- }
+ };
+ auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(big_page_table_cpu[page_index]) << cpu_page_bits) + offset;
+ if constexpr (is_safe) {
+ rasterizer->InvalidateRegion(cpu_addr_base, copy_amount);
+ }
+ if (!IsBigPageContinous(page_index)) [[unlikely]] {
+ memory.WriteBlockUnsafe(cpu_addr_base, src_buffer, copy_amount);
+ } else {
+ u8* physical = memory.GetPointer(cpu_addr_base);
+ std::memcpy(physical, src_buffer, copy_amount);
+ }
+ src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
+ };
+ auto write_short_pages = [&](std::size_t page_index, std::size_t offset,
+ std::size_t copy_amount) {
+ GPUVAddr base = (page_index << big_page_bits) + offset;
+ MemoryOperation<false>(base, copy_amount, mapped_normal, just_advance, just_advance);
+ };
+ MemoryOperation<true>(gpu_dest_addr, size, mapped_big, just_advance, write_short_pages);
}
void MemoryManager::WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size) {
- WriteBlockImpl(gpu_dest_addr, src_buffer, size, true);
+ WriteBlockImpl<true>(gpu_dest_addr, src_buffer, size);
}
void MemoryManager::WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buffer,
std::size_t size) {
- WriteBlockImpl(gpu_dest_addr, src_buffer, size, false);
+ WriteBlockImpl<false>(gpu_dest_addr, src_buffer, size);
}
void MemoryManager::FlushRegion(GPUVAddr gpu_addr, size_t size) const {
- size_t remaining_size{size};
- size_t page_index{gpu_addr >> page_bits};
- size_t page_offset{gpu_addr & page_mask};
- while (remaining_size > 0) {
- const size_t num_bytes{std::min(page_size - page_offset, remaining_size)};
- if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) {
- rasterizer->FlushRegion(*page_addr + page_offset, num_bytes);
+ auto do_nothing = [&]([[maybe_unused]] std::size_t page_index,
+ [[maybe_unused]] std::size_t offset,
+ [[maybe_unused]] std::size_t copy_amount) {};
+
+ auto mapped_normal = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(page_table[page_index]) << cpu_page_bits) + offset;
+ rasterizer->FlushRegion(cpu_addr_base, copy_amount);
+ };
+ auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(big_page_table_cpu[page_index]) << cpu_page_bits) + offset;
+ rasterizer->FlushRegion(cpu_addr_base, copy_amount);
+ };
+ auto flush_short_pages = [&](std::size_t page_index, std::size_t offset,
+ std::size_t copy_amount) {
+ GPUVAddr base = (page_index << big_page_bits) + offset;
+ MemoryOperation<false>(base, copy_amount, mapped_normal, do_nothing, do_nothing);
+ };
+ MemoryOperation<true>(gpu_addr, size, mapped_big, do_nothing, flush_short_pages);
+}
+
+bool MemoryManager::IsMemoryDirty(GPUVAddr gpu_addr, size_t size) const {
+ bool result = false;
+ auto do_nothing = [&]([[maybe_unused]] std::size_t page_index,
+ [[maybe_unused]] std::size_t offset,
+ [[maybe_unused]] std::size_t copy_amount) { return false; };
+
+ auto mapped_normal = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(page_table[page_index]) << cpu_page_bits) + offset;
+ result |= rasterizer->MustFlushRegion(cpu_addr_base, copy_amount);
+ return result;
+ };
+ auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(big_page_table_cpu[page_index]) << cpu_page_bits) + offset;
+ result |= rasterizer->MustFlushRegion(cpu_addr_base, copy_amount);
+ return result;
+ };
+ auto check_short_pages = [&](std::size_t page_index, std::size_t offset,
+ std::size_t copy_amount) {
+ GPUVAddr base = (page_index << big_page_bits) + offset;
+ MemoryOperation<false>(base, copy_amount, mapped_normal, do_nothing, do_nothing);
+ return result;
+ };
+ MemoryOperation<true>(gpu_addr, size, mapped_big, do_nothing, check_short_pages);
+ return result;
+}
+
+size_t MemoryManager::MaxContinousRange(GPUVAddr gpu_addr, size_t size) const {
+ std::optional<VAddr> old_page_addr{};
+ size_t range_so_far = 0;
+ bool result{false};
+ auto fail = [&]([[maybe_unused]] std::size_t page_index, [[maybe_unused]] std::size_t offset,
+ std::size_t copy_amount) {
+ result = true;
+ return true;
+ };
+ auto short_check = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(page_table[page_index]) << cpu_page_bits) + offset;
+ if (old_page_addr && *old_page_addr != cpu_addr_base) {
+ result = true;
+ return true;
}
- ++page_index;
- page_offset = 0;
- remaining_size -= num_bytes;
- }
+ range_so_far += copy_amount;
+ old_page_addr = {cpu_addr_base + copy_amount};
+ return false;
+ };
+ auto big_check = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(big_page_table_cpu[page_index]) << cpu_page_bits) + offset;
+ if (old_page_addr && *old_page_addr != cpu_addr_base) {
+ return true;
+ }
+ range_so_far += copy_amount;
+ old_page_addr = {cpu_addr_base + copy_amount};
+ return false;
+ };
+ auto check_short_pages = [&](std::size_t page_index, std::size_t offset,
+ std::size_t copy_amount) {
+ GPUVAddr base = (page_index << big_page_bits) + offset;
+ MemoryOperation<false>(base, copy_amount, short_check, fail, fail);
+ return result;
+ };
+ MemoryOperation<true>(gpu_addr, size, big_check, fail, check_short_pages);
+ return range_so_far;
+}
+
+void MemoryManager::InvalidateRegion(GPUVAddr gpu_addr, size_t size) const {
+ auto do_nothing = [&]([[maybe_unused]] std::size_t page_index,
+ [[maybe_unused]] std::size_t offset,
+ [[maybe_unused]] std::size_t copy_amount) {};
+
+ auto mapped_normal = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(page_table[page_index]) << cpu_page_bits) + offset;
+ rasterizer->InvalidateRegion(cpu_addr_base, copy_amount);
+ };
+ auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(big_page_table_cpu[page_index]) << cpu_page_bits) + offset;
+ rasterizer->InvalidateRegion(cpu_addr_base, copy_amount);
+ };
+ auto invalidate_short_pages = [&](std::size_t page_index, std::size_t offset,
+ std::size_t copy_amount) {
+ GPUVAddr base = (page_index << big_page_bits) + offset;
+ MemoryOperation<false>(base, copy_amount, mapped_normal, do_nothing, do_nothing);
+ };
+ MemoryOperation<true>(gpu_addr, size, mapped_big, do_nothing, invalidate_short_pages);
}
void MemoryManager::CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size) {
@@ -367,87 +564,134 @@ void MemoryManager::CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std
}
bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const {
- const auto cpu_addr{GpuToCpuAddress(gpu_addr)};
- if (!cpu_addr) {
+ if (GetEntry<true>(gpu_addr) == EntryType::Mapped) [[likely]] {
+ size_t page_index = gpu_addr >> big_page_bits;
+ if (IsBigPageContinous(page_index)) [[likely]] {
+ const std::size_t page{(page_index & big_page_mask) + size};
+ return page <= big_page_size;
+ }
+ const std::size_t page{(gpu_addr & Core::Memory::YUZU_PAGEMASK) + size};
+ return page <= Core::Memory::YUZU_PAGESIZE;
+ }
+ if (GetEntry<false>(gpu_addr) != EntryType::Mapped) {
return false;
}
- const std::size_t page{(*cpu_addr & Core::Memory::PAGE_MASK) + size};
- return page <= Core::Memory::PAGE_SIZE;
+ const std::size_t page{(gpu_addr & Core::Memory::YUZU_PAGEMASK) + size};
+ return page <= Core::Memory::YUZU_PAGESIZE;
}
bool MemoryManager::IsContinousRange(GPUVAddr gpu_addr, std::size_t size) const {
- size_t page_index{gpu_addr >> page_bits};
- const size_t page_last{(gpu_addr + size + page_size - 1) >> page_bits};
std::optional<VAddr> old_page_addr{};
- while (page_index != page_last) {
- const auto page_addr{GpuToCpuAddress(page_index << page_bits)};
- if (!page_addr || *page_addr == 0) {
- return false;
+ bool result{true};
+ auto fail = [&]([[maybe_unused]] std::size_t page_index, [[maybe_unused]] std::size_t offset,
+ std::size_t copy_amount) {
+ result = false;
+ return true;
+ };
+ auto short_check = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(page_table[page_index]) << cpu_page_bits) + offset;
+ if (old_page_addr && *old_page_addr != cpu_addr_base) {
+ result = false;
+ return true;
}
- if (old_page_addr) {
- if (*old_page_addr + page_size != *page_addr) {
- return false;
- }
+ old_page_addr = {cpu_addr_base + copy_amount};
+ return false;
+ };
+ auto big_check = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(big_page_table_cpu[page_index]) << cpu_page_bits) + offset;
+ if (old_page_addr && *old_page_addr != cpu_addr_base) {
+ result = false;
+ return true;
}
- old_page_addr = page_addr;
- ++page_index;
- }
- return true;
+ old_page_addr = {cpu_addr_base + copy_amount};
+ return false;
+ };
+ auto check_short_pages = [&](std::size_t page_index, std::size_t offset,
+ std::size_t copy_amount) {
+ GPUVAddr base = (page_index << big_page_bits) + offset;
+ MemoryOperation<false>(base, copy_amount, short_check, fail, fail);
+ return !result;
+ };
+ MemoryOperation<true>(gpu_addr, size, big_check, fail, check_short_pages);
+ return result;
}
bool MemoryManager::IsFullyMappedRange(GPUVAddr gpu_addr, std::size_t size) const {
- size_t page_index{gpu_addr >> page_bits};
- const size_t page_last{(gpu_addr + size + page_size - 1) >> page_bits};
- while (page_index < page_last) {
- if (!page_table[page_index].IsValid() || page_table[page_index].ToAddress() == 0) {
- return false;
- }
- ++page_index;
- }
- return true;
+ bool result{true};
+ auto fail = [&]([[maybe_unused]] std::size_t page_index, [[maybe_unused]] std::size_t offset,
+ [[maybe_unused]] std::size_t copy_amount) {
+ result = false;
+ return true;
+ };
+ auto pass = [&]([[maybe_unused]] std::size_t page_index, [[maybe_unused]] std::size_t offset,
+ [[maybe_unused]] std::size_t copy_amount) { return false; };
+ auto check_short_pages = [&](std::size_t page_index, std::size_t offset,
+ std::size_t copy_amount) {
+ GPUVAddr base = (page_index << big_page_bits) + offset;
+ MemoryOperation<false>(base, copy_amount, pass, pass, fail);
+ return !result;
+ };
+ MemoryOperation<true>(gpu_addr, size, pass, fail, check_short_pages);
+ return result;
}
std::vector<std::pair<GPUVAddr, std::size_t>> MemoryManager::GetSubmappedRange(
GPUVAddr gpu_addr, std::size_t size) const {
std::vector<std::pair<GPUVAddr, std::size_t>> result{};
- size_t page_index{gpu_addr >> page_bits};
- size_t remaining_size{size};
- size_t page_offset{gpu_addr & page_mask};
std::optional<std::pair<GPUVAddr, std::size_t>> last_segment{};
std::optional<VAddr> old_page_addr{};
- const auto extend_size = [&last_segment, &page_index, &page_offset](std::size_t bytes) {
- if (!last_segment) {
- const GPUVAddr new_base_addr = (page_index << page_bits) + page_offset;
- last_segment = {new_base_addr, bytes};
- } else {
- last_segment->second += bytes;
- }
- };
- const auto split = [&last_segment, &result] {
+ const auto split = [&last_segment, &result]([[maybe_unused]] std::size_t page_index,
+ [[maybe_unused]] std::size_t offset,
+ [[maybe_unused]] std::size_t copy_amount) {
if (last_segment) {
result.push_back(*last_segment);
last_segment = std::nullopt;
}
};
- while (remaining_size > 0) {
- const size_t num_bytes{std::min(page_size - page_offset, remaining_size)};
- const auto page_addr{GpuToCpuAddress(page_index << page_bits)};
- if (!page_addr || *page_addr == 0) {
- split();
- } else if (old_page_addr) {
- if (*old_page_addr + page_size != *page_addr) {
- split();
+ const auto extend_size_big = [this, &split, &old_page_addr,
+ &last_segment](std::size_t page_index, std::size_t offset,
+ std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(big_page_table_cpu[page_index]) << cpu_page_bits) + offset;
+ if (old_page_addr) {
+ if (*old_page_addr != cpu_addr_base) {
+ split(0, 0, 0);
+ }
+ }
+ old_page_addr = {cpu_addr_base + copy_amount};
+ if (!last_segment) {
+ const GPUVAddr new_base_addr = (page_index << big_page_bits) + offset;
+ last_segment = {new_base_addr, copy_amount};
+ } else {
+ last_segment->second += copy_amount;
+ }
+ };
+ const auto extend_size_short = [this, &split, &old_page_addr,
+ &last_segment](std::size_t page_index, std::size_t offset,
+ std::size_t copy_amount) {
+ const VAddr cpu_addr_base =
+ (static_cast<VAddr>(page_table[page_index]) << cpu_page_bits) + offset;
+ if (old_page_addr) {
+ if (*old_page_addr != cpu_addr_base) {
+ split(0, 0, 0);
}
- extend_size(num_bytes);
+ }
+ old_page_addr = {cpu_addr_base + copy_amount};
+ if (!last_segment) {
+ const GPUVAddr new_base_addr = (page_index << page_bits) + offset;
+ last_segment = {new_base_addr, copy_amount};
} else {
- extend_size(num_bytes);
+ last_segment->second += copy_amount;
}
- ++page_index;
- page_offset = 0;
- remaining_size -= num_bytes;
- old_page_addr = page_addr;
- }
- split();
+ };
+ auto do_short_pages = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
+ GPUVAddr base = (page_index << big_page_bits) + offset;
+ MemoryOperation<false>(base, copy_amount, extend_size_short, split, split);
+ };
+ MemoryOperation<true>(gpu_addr, size, extend_size_big, split, do_short_pages);
+ split(0, 0, 0);
return result;
}
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index 61bfe47c7..f992e29f3 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -1,76 +1,41 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include <atomic>
#include <map>
#include <optional>
#include <vector>
#include "common/common_types.h"
+#include "common/multi_level_page_table.h"
+#include "common/virtual_buffer.h"
namespace VideoCore {
class RasterizerInterface;
}
namespace Core {
+class DeviceMemory;
+namespace Memory {
+class Memory;
+} // namespace Memory
class System;
-}
+} // namespace Core
namespace Tegra {
-class PageEntry final {
-public:
- enum class State : u32 {
- Unmapped = static_cast<u32>(-1),
- Allocated = static_cast<u32>(-2),
- };
-
- constexpr PageEntry() = default;
- constexpr PageEntry(State state_) : state{state_} {}
- constexpr PageEntry(VAddr addr) : state{static_cast<State>(addr >> ShiftBits)} {}
-
- [[nodiscard]] constexpr bool IsUnmapped() const {
- return state == State::Unmapped;
- }
-
- [[nodiscard]] constexpr bool IsAllocated() const {
- return state == State::Allocated;
- }
-
- [[nodiscard]] constexpr bool IsValid() const {
- return !IsUnmapped() && !IsAllocated();
- }
-
- [[nodiscard]] constexpr VAddr ToAddress() const {
- if (!IsValid()) {
- return {};
- }
-
- return static_cast<VAddr>(state) << ShiftBits;
- }
-
- [[nodiscard]] constexpr PageEntry operator+(u64 offset) const {
- // If this is a reserved value, offsets do not apply
- if (!IsValid()) {
- return *this;
- }
- return PageEntry{(static_cast<VAddr>(state) << ShiftBits) + offset};
- }
-
-private:
- static constexpr std::size_t ShiftBits{12};
-
- State state{State::Unmapped};
-};
-static_assert(sizeof(PageEntry) == 4, "PageEntry is too large");
-
class MemoryManager final {
public:
- explicit MemoryManager(Core::System& system_);
+ explicit MemoryManager(Core::System& system_, u64 address_space_bits_ = 40,
+ u64 big_page_bits_ = 16, u64 page_bits_ = 12);
~MemoryManager();
+ size_t GetID() const {
+ return unique_identifier;
+ }
+
/// Binds a renderer to the memory manager.
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
@@ -87,9 +52,6 @@ public:
[[nodiscard]] u8* GetPointer(GPUVAddr addr);
[[nodiscard]] const u8* GetPointer(GPUVAddr addr) const;
- /// Returns the number of bytes until the end of the memory map containing the given GPU address
- [[nodiscard]] size_t BytesToMapEnd(GPUVAddr gpu_addr) const noexcept;
-
/**
* ReadBlock and WriteBlock are full read and write operations over virtual
* GPU Memory. It's important to use these when GPU memory may not be continuous
@@ -136,54 +98,95 @@ public:
std::vector<std::pair<GPUVAddr, std::size_t>> GetSubmappedRange(GPUVAddr gpu_addr,
std::size_t size) const;
- [[nodiscard]] GPUVAddr Map(VAddr cpu_addr, GPUVAddr gpu_addr, std::size_t size);
- [[nodiscard]] GPUVAddr MapAllocate(VAddr cpu_addr, std::size_t size, std::size_t align);
- [[nodiscard]] GPUVAddr MapAllocate32(VAddr cpu_addr, std::size_t size);
- [[nodiscard]] std::optional<GPUVAddr> AllocateFixed(GPUVAddr gpu_addr, std::size_t size);
- [[nodiscard]] GPUVAddr Allocate(std::size_t size, std::size_t align);
+ GPUVAddr Map(GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size, bool is_big_pages = true);
+ GPUVAddr MapSparse(GPUVAddr gpu_addr, std::size_t size, bool is_big_pages = true);
void Unmap(GPUVAddr gpu_addr, std::size_t size);
void FlushRegion(GPUVAddr gpu_addr, size_t size) const;
+ void InvalidateRegion(GPUVAddr gpu_addr, size_t size) const;
+
+ bool IsMemoryDirty(GPUVAddr gpu_addr, size_t size) const;
+
+ size_t MaxContinousRange(GPUVAddr gpu_addr, size_t size) const;
+
+ bool IsWithinGPUAddressRange(GPUVAddr gpu_addr) const {
+ return gpu_addr < address_space_size;
+ }
+
private:
- [[nodiscard]] PageEntry GetPageEntry(GPUVAddr gpu_addr) const;
- void SetPageEntry(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size = page_size);
- GPUVAddr UpdateRange(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size);
- [[nodiscard]] std::optional<GPUVAddr> FindFreeRange(std::size_t size, std::size_t align,
- bool start_32bit_address = false) const;
-
- void TryLockPage(PageEntry page_entry, std::size_t size);
- void TryUnlockPage(PageEntry page_entry, std::size_t size);
-
- void ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size,
- bool is_safe) const;
- void WriteBlockImpl(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size,
- bool is_safe);
-
- [[nodiscard]] static constexpr std::size_t PageEntryIndex(GPUVAddr gpu_addr) {
- return (gpu_addr >> page_bits) & page_table_mask;
+ template <bool is_big_pages, typename FuncMapped, typename FuncReserved, typename FuncUnmapped>
+ inline void MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size, FuncMapped&& func_mapped,
+ FuncReserved&& func_reserved, FuncUnmapped&& func_unmapped) const;
+
+ template <bool is_safe>
+ void ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size) const;
+
+ template <bool is_safe>
+ void WriteBlockImpl(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size);
+
+ template <bool is_big_page>
+ [[nodiscard]] std::size_t PageEntryIndex(GPUVAddr gpu_addr) const {
+ if constexpr (is_big_page) {
+ return (gpu_addr >> big_page_bits) & big_page_table_mask;
+ } else {
+ return (gpu_addr >> page_bits) & page_table_mask;
+ }
}
- static constexpr u64 address_space_size = 1ULL << 40;
- static constexpr u64 address_space_start = 1ULL << 32;
- static constexpr u64 address_space_start_low = 1ULL << 16;
- static constexpr u64 page_bits{16};
- static constexpr u64 page_size{1 << page_bits};
- static constexpr u64 page_mask{page_size - 1};
- static constexpr u64 page_table_bits{24};
- static constexpr u64 page_table_size{1 << page_table_bits};
- static constexpr u64 page_table_mask{page_table_size - 1};
+ inline bool IsBigPageContinous(size_t big_page_index) const;
+ inline void SetBigPageContinous(size_t big_page_index, bool value);
Core::System& system;
+ Core::Memory::Memory& memory;
+ Core::DeviceMemory& device_memory;
+
+ const u64 address_space_bits;
+ const u64 page_bits;
+ u64 address_space_size;
+ u64 page_size;
+ u64 page_mask;
+ u64 page_table_mask;
+ static constexpr u64 cpu_page_bits{12};
+
+ const u64 big_page_bits;
+ u64 big_page_size;
+ u64 big_page_mask;
+ u64 big_page_table_mask;
VideoCore::RasterizerInterface* rasterizer = nullptr;
- std::vector<PageEntry> page_table;
+ enum class EntryType : u64 {
+ Free = 0,
+ Reserved = 1,
+ Mapped = 2,
+ };
+
+ std::vector<u64> entries;
+ std::vector<u64> big_entries;
+
+ template <EntryType entry_type>
+ GPUVAddr PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cpu_addr, size_t size);
+
+ template <EntryType entry_type>
+ GPUVAddr BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cpu_addr, size_t size);
+
+ template <bool is_big_page>
+ inline EntryType GetEntry(size_t position) const;
+
+ template <bool is_big_page>
+ inline void SetEntry(size_t position, EntryType entry);
+
+ Common::MultiLevelPageTable<u32> page_table;
+ Common::VirtualBuffer<u32> big_page_table_cpu;
+
+ std::vector<u64> big_page_continous;
+
+ constexpr static size_t continous_bits = 64;
- using MapRange = std::pair<GPUVAddr, size_t>;
- std::vector<MapRange> map_ranges;
+ const size_t unique_identifier;
- std::vector<std::pair<VAddr, std::size_t>> cache_invalidate_queue;
+ static std::atomic<size_t> unique_identifier_generator;
};
} // namespace Tegra
diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h
index 392f82eb7..b0ebe71b7 100644
--- a/src/video_core/query_cache.h
+++ b/src/video_core/query_cache.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -18,9 +17,8 @@
#include "common/assert.h"
#include "common/settings.h"
-#include "core/core.h"
+#include "video_core/control/channel_state_cache.h"
#include "video_core/engines/maxwell_3d.h"
-#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
@@ -93,13 +91,10 @@ private:
};
template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter>
-class QueryCacheBase {
+class QueryCacheBase : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
public:
- explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_,
- Tegra::Engines::Maxwell3D& maxwell3d_,
- Tegra::MemoryManager& gpu_memory_)
- : rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
- gpu_memory{gpu_memory_}, streams{{CounterStream{static_cast<QueryCache&>(*this),
+ explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_)
+ : rasterizer{rasterizer_}, streams{{CounterStream{static_cast<QueryCache&>(*this),
VideoCore::QueryType::SamplesPassed}}} {}
void InvalidateRegion(VAddr addr, std::size_t size) {
@@ -120,13 +115,13 @@ public:
*/
void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) {
std::unique_lock lock{mutex};
- const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
ASSERT(cpu_addr);
CachedQuery* query = TryGet(*cpu_addr);
if (!query) {
ASSERT_OR_EXECUTE(cpu_addr, return;);
- u8* const host_ptr = gpu_memory.GetPointer(gpu_addr);
+ u8* const host_ptr = gpu_memory->GetPointer(gpu_addr);
query = Register(type, *cpu_addr, host_ptr, timestamp.has_value());
}
@@ -140,8 +135,10 @@ public:
/// Updates counters from GPU state. Expected to be called once per draw, clear or dispatch.
void UpdateCounters() {
std::unique_lock lock{mutex};
- const auto& regs = maxwell3d.regs;
- Stream(VideoCore::QueryType::SamplesPassed).Update(regs.samplecnt_enable);
+ if (maxwell3d) {
+ const auto& regs = maxwell3d->regs;
+ Stream(VideoCore::QueryType::SamplesPassed).Update(regs.samplecnt_enable);
+ }
}
/// Resets a counter to zero. It doesn't disable the query after resetting.
@@ -217,8 +214,8 @@ private:
return cache_begin < addr_end && addr_begin < cache_end;
};
- const u64 page_end = addr_end >> PAGE_BITS;
- for (u64 page = addr_begin >> PAGE_BITS; page <= page_end; ++page) {
+ const u64 page_end = addr_end >> YUZU_PAGEBITS;
+ for (u64 page = addr_begin >> YUZU_PAGEBITS; page <= page_end; ++page) {
const auto& it = cached_queries.find(page);
if (it == std::end(cached_queries)) {
continue;
@@ -238,14 +235,14 @@ private:
/// Registers the passed parameters as cached and returns a pointer to the stored cached query.
CachedQuery* Register(VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr, bool timestamp) {
rasterizer.UpdatePagesCachedCount(cpu_addr, CachedQuery::SizeInBytes(timestamp), 1);
- const u64 page = static_cast<u64>(cpu_addr) >> PAGE_BITS;
+ const u64 page = static_cast<u64>(cpu_addr) >> YUZU_PAGEBITS;
return &cached_queries[page].emplace_back(static_cast<QueryCache&>(*this), type, cpu_addr,
host_ptr);
}
/// Tries to a get a cached query. Returns nullptr on failure.
CachedQuery* TryGet(VAddr addr) {
- const u64 page = static_cast<u64>(addr) >> PAGE_BITS;
+ const u64 page = static_cast<u64>(addr) >> YUZU_PAGEBITS;
const auto it = cached_queries.find(page);
if (it == std::end(cached_queries)) {
return nullptr;
@@ -263,12 +260,10 @@ private:
uncommitted_flushes->push_back(addr);
}
- static constexpr std::uintptr_t PAGE_SIZE = 4096;
- static constexpr unsigned PAGE_BITS = 12;
+ static constexpr std::uintptr_t YUZU_PAGESIZE = 4096;
+ static constexpr unsigned YUZU_PAGEBITS = 12;
VideoCore::RasterizerInterface& rasterizer;
- Tegra::Engines::Maxwell3D& maxwell3d;
- Tegra::MemoryManager& gpu_memory;
std::recursive_mutex mutex;
diff --git a/src/video_core/rasterizer_accelerated.cpp b/src/video_core/rasterizer_accelerated.cpp
index 4c9524702..4a197d65d 100644
--- a/src/video_core/rasterizer_accelerated.cpp
+++ b/src/video_core/rasterizer_accelerated.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
@@ -25,8 +24,8 @@ void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int del
u64 cache_bytes = 0;
std::atomic_thread_fence(std::memory_order_acquire);
- const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE);
- for (u64 page = addr >> PAGE_BITS; page != page_end; ++page) {
+ const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE);
+ for (u64 page = addr >> YUZU_PAGEBITS; page != page_end; ++page) {
std::atomic_uint16_t& count = cached_pages.at(page >> 2).Count(page);
if (delta > 0) {
@@ -45,26 +44,27 @@ void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int del
if (uncache_bytes == 0) {
uncache_begin = page;
}
- uncache_bytes += PAGE_SIZE;
+ uncache_bytes += YUZU_PAGESIZE;
} else if (uncache_bytes > 0) {
- cpu_memory.RasterizerMarkRegionCached(uncache_begin << PAGE_BITS, uncache_bytes, false);
+ cpu_memory.RasterizerMarkRegionCached(uncache_begin << YUZU_PAGEBITS, uncache_bytes,
+ false);
uncache_bytes = 0;
}
if (count.load(std::memory_order::relaxed) == 1 && delta > 0) {
if (cache_bytes == 0) {
cache_begin = page;
}
- cache_bytes += PAGE_SIZE;
+ cache_bytes += YUZU_PAGESIZE;
} else if (cache_bytes > 0) {
- cpu_memory.RasterizerMarkRegionCached(cache_begin << PAGE_BITS, cache_bytes, true);
+ cpu_memory.RasterizerMarkRegionCached(cache_begin << YUZU_PAGEBITS, cache_bytes, true);
cache_bytes = 0;
}
}
if (uncache_bytes > 0) {
- cpu_memory.RasterizerMarkRegionCached(uncache_begin << PAGE_BITS, uncache_bytes, false);
+ cpu_memory.RasterizerMarkRegionCached(uncache_begin << YUZU_PAGEBITS, uncache_bytes, false);
}
if (cache_bytes > 0) {
- cpu_memory.RasterizerMarkRegionCached(cache_begin << PAGE_BITS, cache_bytes, true);
+ cpu_memory.RasterizerMarkRegionCached(cache_begin << YUZU_PAGEBITS, cache_bytes, true);
}
}
diff --git a/src/video_core/rasterizer_accelerated.h b/src/video_core/rasterizer_accelerated.h
index 249644e50..7118b8aff 100644
--- a/src/video_core/rasterizer_accelerated.h
+++ b/src/video_core/rasterizer_accelerated.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index 1f1f12291..d2d40884c 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -17,6 +16,9 @@ class MemoryManager;
namespace Engines {
class AccelerateDMAInterface;
}
+namespace Control {
+struct ChannelState;
+}
} // namespace Tegra
namespace VideoCore {
@@ -60,7 +62,10 @@ public:
virtual void DisableGraphicsUniformBuffer(size_t stage, u32 index) = 0;
/// Signal a GPU based semaphore as a fence
- virtual void SignalSemaphore(GPUVAddr addr, u32 value) = 0;
+ virtual void SignalFence(std::function<void()>&& func) = 0;
+
+ /// Send an operation to be done after a certain amount of flushes.
+ virtual void SyncOperation(std::function<void()>&& func) = 0;
/// Signal a GPU based syncpoint as a fence
virtual void SignalSyncPoint(u32 value) = 0;
@@ -87,13 +92,13 @@ public:
virtual void OnCPUWrite(VAddr addr, u64 size) = 0;
/// Sync memory between guest and host.
- virtual void SyncGuestHost() = 0;
+ virtual void InvalidateGPUCache() = 0;
/// Unmap memory range
virtual void UnmapMemory(VAddr addr, u64 size) = 0;
/// Remap GPU memory range. This means underneath backing memory changed
- virtual void ModifyGPUMemory(GPUVAddr addr, u64 size) = 0;
+ virtual void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) = 0;
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
/// and invalidated
@@ -124,7 +129,7 @@ public:
[[nodiscard]] virtual Tegra::Engines::AccelerateDMAInterface& AccessAccelerateDMA() = 0;
virtual void AccelerateInlineToMemory(GPUVAddr address, size_t copy_size,
- std::span<u8> memory) = 0;
+ std::span<const u8> memory) = 0;
/// Attempt to use a faster method to display the framebuffer to screen
[[nodiscard]] virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& config,
@@ -138,5 +143,11 @@ public:
/// Initialize disk cached resources for the game being emulated
virtual void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
const DiskResourceLoadCallback& callback) {}
+
+ virtual void InitializeChannel(Tegra::Control::ChannelState& channel) {}
+
+ virtual void BindChannel(Tegra::Control::ChannelState& channel) {}
+
+ virtual void ReleaseChannel(s32 channel_id) {}
};
} // namespace VideoCore
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index a99c33c37..45791aa75 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -1,9 +1,7 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
-#include "common/settings.h"
#include "core/frontend/emu_window.h"
#include "video_core/renderer_base.h"
@@ -27,6 +25,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..8d20cbece 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -83,6 +82,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..08f4d69ab 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <span>
@@ -135,6 +134,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,
@@ -155,7 +168,7 @@ void BufferCacheRuntime::BindIndexBuffer(Buffer& buffer, u32 offset, u32 size) {
if (has_unified_vertex_buffers) {
buffer.MakeResident(GL_READ_ONLY);
glBufferAddressRangeNV(GL_ELEMENT_ARRAY_ADDRESS_NV, 0, buffer.HostGpuAddr() + offset,
- static_cast<GLsizeiptr>(size));
+ static_cast<GLsizeiptr>(Common::AlignUp(size, 4)));
} else {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.Handle());
index_buffer_offset = offset;
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index 060d36427..a8c3f8b67 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -1,15 +1,12 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <span>
-#include "common/alignment.h"
#include "common/common_types.h"
-#include "common/dynamic_library.h"
#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_opengl/gl_device.h"
@@ -91,6 +88,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);
@@ -153,6 +152,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,
@@ -186,6 +193,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_compute_pipeline.cpp b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
index 5c1f21c65..26d066004 100644
--- a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
@@ -29,12 +28,11 @@ bool ComputePipelineKey::operator==(const ComputePipelineKey& rhs) const noexcep
}
ComputePipeline::ComputePipeline(const Device& device, TextureCache& texture_cache_,
- BufferCache& buffer_cache_, Tegra::MemoryManager& gpu_memory_,
- Tegra::Engines::KeplerCompute& kepler_compute_,
- ProgramManager& program_manager_, const Shader::Info& info_,
- std::string code, std::vector<u32> code_v)
- : texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, gpu_memory{gpu_memory_},
- kepler_compute{kepler_compute_}, program_manager{program_manager_}, info{info_} {
+ BufferCache& buffer_cache_, ProgramManager& program_manager_,
+ const Shader::Info& info_, std::string code,
+ std::vector<u32> code_v)
+ : texture_cache{texture_cache_}, buffer_cache{buffer_cache_},
+ program_manager{program_manager_}, info{info_} {
switch (device.GetShaderBackend()) {
case Settings::ShaderBackend::GLSL:
source_program = CreateProgram(code, GL_COMPUTE_SHADER);
@@ -87,7 +85,7 @@ void ComputePipeline::Configure() {
GLsizei texture_binding{};
GLsizei image_binding{};
- const auto& qmd{kepler_compute.launch_description};
+ const auto& qmd{kepler_compute->launch_description};
const auto& cbufs{qmd.const_buffer_config};
const bool via_header_index{qmd.linked_tsc != 0};
const auto read_handle{[&](const auto& desc, u32 index) {
@@ -102,12 +100,13 @@ void ComputePipeline::Configure() {
const u32 secondary_offset{desc.secondary_cbuf_offset + index_offset};
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].Address() +
secondary_offset};
- const u32 lhs_raw{gpu_memory.Read<u32>(addr)};
- const u32 rhs_raw{gpu_memory.Read<u32>(separate_addr)};
+ const u32 lhs_raw{gpu_memory->Read<u32>(addr) << desc.shift_left};
+ const u32 rhs_raw{gpu_memory->Read<u32>(separate_addr)
+ << desc.secondary_shift_left};
return TexturePair(lhs_raw | rhs_raw, via_header_index);
}
}
- return TexturePair(gpu_memory.Read<u32>(addr), via_header_index);
+ return TexturePair(gpu_memory->Read<u32>(addr), via_header_index);
}};
const auto add_image{[&](const auto& desc, bool blacklist) {
for (u32 index = 0; index < desc.count; ++index) {
diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.h b/src/video_core/renderer_opengl/gl_compute_pipeline.h
index 50c676365..6534dec32 100644
--- a/src/video_core/renderer_opengl/gl_compute_pipeline.h
+++ b/src/video_core/renderer_opengl/gl_compute_pipeline.h
@@ -1,12 +1,10 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <type_traits>
-#include <utility>
#include "common/common_types.h"
#include "shader_recompiler/shader_info.h"
@@ -51,10 +49,8 @@ static_assert(std::is_trivially_constructible_v<ComputePipelineKey>);
class ComputePipeline {
public:
explicit ComputePipeline(const Device& device, TextureCache& texture_cache_,
- BufferCache& buffer_cache_, Tegra::MemoryManager& gpu_memory_,
- Tegra::Engines::KeplerCompute& kepler_compute_,
- ProgramManager& program_manager_, const Shader::Info& info_,
- std::string code, std::vector<u32> code_v);
+ BufferCache& buffer_cache_, ProgramManager& program_manager_,
+ const Shader::Info& info_, std::string code, std::vector<u32> code_v);
void Configure();
@@ -62,11 +58,17 @@ public:
return writes_global_memory;
}
+ void SetEngine(Tegra::Engines::KeplerCompute* kepler_compute_,
+ Tegra::MemoryManager* gpu_memory_) {
+ kepler_compute = kepler_compute_;
+ gpu_memory = gpu_memory_;
+ }
+
private:
TextureCache& texture_cache;
BufferCache& buffer_cache;
- Tegra::MemoryManager& gpu_memory;
- Tegra::Engines::KeplerCompute& kepler_compute;
+ Tegra::MemoryManager* gpu_memory;
+ Tegra::Engines::KeplerCompute* kepler_compute;
ProgramManager& program_manager;
Shader::Info info;
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index e62912a22..1663e277d 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -1,13 +1,10 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdlib>
-#include <cstring>
-#include <limits>
#include <optional>
#include <span>
#include <stdexcept>
@@ -15,13 +12,15 @@
#include <glad/glad.h>
+#include "common/literals.h"
#include "common/logging/log.h"
-#include "common/scope_exit.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 = {
@@ -168,6 +167,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"
@@ -279,4 +279,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..5ef51ebcf 100644
--- a/src/video_core/renderer_opengl/gl_device.h
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -20,6 +19,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 +169,10 @@ public:
return vendor_name == "ATI Technologies Inc.";
}
+ bool CanReportMemoryUsage() const {
+ return can_report_memory;
+ }
+
private:
static bool TestVariableAoffi();
static bool TestPreciseBug();
@@ -210,6 +215,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_fence_manager.cpp b/src/video_core/renderer_opengl/gl_fence_manager.cpp
index 151290101..91463f854 100644
--- a/src/video_core/renderer_opengl/gl_fence_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_fence_manager.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
@@ -11,10 +10,7 @@
namespace OpenGL {
-GLInnerFence::GLInnerFence(u32 payload_, bool is_stubbed_) : FenceBase{payload_, is_stubbed_} {}
-
-GLInnerFence::GLInnerFence(GPUVAddr address_, u32 payload_, bool is_stubbed_)
- : FenceBase{address_, payload_, is_stubbed_} {}
+GLInnerFence::GLInnerFence(bool is_stubbed_) : FenceBase{is_stubbed_} {}
GLInnerFence::~GLInnerFence() = default;
@@ -31,9 +27,8 @@ bool GLInnerFence::IsSignaled() const {
return true;
}
ASSERT(sync_object.handle != 0);
- GLsizei length;
GLint sync_status;
- glGetSynciv(sync_object.handle, GL_SYNC_STATUS, sizeof(GLint), &length, &sync_status);
+ glGetSynciv(sync_object.handle, GL_SYNC_STATUS, 1, nullptr, &sync_status);
return sync_status == GL_SIGNALED;
}
@@ -50,12 +45,8 @@ FenceManagerOpenGL::FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterize
BufferCache& buffer_cache_, QueryCache& query_cache_)
: GenericFenceManager{rasterizer_, gpu_, texture_cache_, buffer_cache_, query_cache_} {}
-Fence FenceManagerOpenGL::CreateFence(u32 value, bool is_stubbed) {
- return std::make_shared<GLInnerFence>(value, is_stubbed);
-}
-
-Fence FenceManagerOpenGL::CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) {
- return std::make_shared<GLInnerFence>(addr, value, is_stubbed);
+Fence FenceManagerOpenGL::CreateFence(bool is_stubbed) {
+ return std::make_shared<GLInnerFence>(is_stubbed);
}
void FenceManagerOpenGL::QueueFence(Fence& fence) {
diff --git a/src/video_core/renderer_opengl/gl_fence_manager.h b/src/video_core/renderer_opengl/gl_fence_manager.h
index e714aa115..f1446e732 100644
--- a/src/video_core/renderer_opengl/gl_fence_manager.h
+++ b/src/video_core/renderer_opengl/gl_fence_manager.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -17,8 +16,7 @@ namespace OpenGL {
class GLInnerFence : public VideoCommon::FenceBase {
public:
- explicit GLInnerFence(u32 payload_, bool is_stubbed_);
- explicit GLInnerFence(GPUVAddr address_, u32 payload_, bool is_stubbed_);
+ explicit GLInnerFence(bool is_stubbed_);
~GLInnerFence();
void Queue();
@@ -41,8 +39,7 @@ public:
QueryCache& query_cache);
protected:
- Fence CreateFence(u32 value, bool is_stubbed) override;
- Fence CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) override;
+ Fence CreateFence(bool is_stubbed) override;
void QueueFence(Fence& fence) override;
bool IsFenceSignaled(Fence& fence) const override;
void WaitFence(Fence& fence) override;
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
index f8495896c..41493a7da 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -49,7 +48,7 @@ GLenum Stage(size_t stage_index) {
case 4:
return GL_FRAGMENT_SHADER;
}
- UNREACHABLE_MSG("{}", stage_index);
+ ASSERT_MSG(false, "{}", stage_index);
return GL_NONE;
}
@@ -66,7 +65,7 @@ GLenum AssemblyStage(size_t stage_index) {
case 4:
return GL_FRAGMENT_PROGRAM_NV;
}
- UNREACHABLE_MSG("{}", stage_index);
+ ASSERT_MSG(false, "{}", stage_index);
return GL_NONE;
}
@@ -170,15 +169,15 @@ ConfigureFuncPtr ConfigureFunc(const std::array<Shader::Info, 5>& infos, u32 ena
}
} // Anonymous namespace
-GraphicsPipeline::GraphicsPipeline(
- const Device& device, TextureCache& texture_cache_, BufferCache& buffer_cache_,
- Tegra::MemoryManager& gpu_memory_, Tegra::Engines::Maxwell3D& maxwell3d_,
- ProgramManager& program_manager_, StateTracker& state_tracker_, ShaderWorker* thread_worker,
- VideoCore::ShaderNotify* shader_notify, std::array<std::string, 5> sources,
- std::array<std::vector<u32>, 5> sources_spirv, const std::array<const Shader::Info*, 5>& infos,
- const GraphicsPipelineKey& key_)
- : texture_cache{texture_cache_}, buffer_cache{buffer_cache_},
- gpu_memory{gpu_memory_}, maxwell3d{maxwell3d_}, program_manager{program_manager_},
+GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_cache_,
+ BufferCache& buffer_cache_, ProgramManager& program_manager_,
+ StateTracker& state_tracker_, ShaderWorker* thread_worker,
+ VideoCore::ShaderNotify* shader_notify,
+ std::array<std::string, 5> sources,
+ std::array<std::vector<u32>, 5> sources_spirv,
+ const std::array<const Shader::Info*, 5>& infos,
+ const GraphicsPipelineKey& key_)
+ : texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, program_manager{program_manager_},
state_tracker{state_tracker_}, key{key_} {
if (shader_notify) {
shader_notify->MarkShaderBuilding();
@@ -243,10 +242,6 @@ GraphicsPipeline::GraphicsPipeline(
case Settings::ShaderBackend::GLASM:
if (!sources[stage].empty()) {
assembly_programs[stage] = CompileProgram(sources[stage], AssemblyStage(stage));
- if (in_parallel) {
- // Make sure program is built before continuing when building in parallel
- glGetString(GL_PROGRAM_ERROR_STRING_NV);
- }
}
break;
case Settings::ShaderBackend::SPIRV:
@@ -256,20 +251,18 @@ GraphicsPipeline::GraphicsPipeline(
break;
}
}
- if (in_parallel && backend != Settings::ShaderBackend::GLASM) {
- // Make sure programs have built if we are building shaders in parallel
- for (OGLProgram& program : source_programs) {
- if (program.handle != 0) {
- GLint status{};
- glGetProgramiv(program.handle, GL_LINK_STATUS, &status);
- }
- }
+ if (in_parallel) {
+ std::scoped_lock lock{built_mutex};
+ built_fence.Create();
+ // Flush this context to ensure compilation commands and fence are in the GPU pipe.
+ glFlush();
+ built_condvar.notify_one();
+ } else {
+ is_built = true;
}
if (shader_notify) {
shader_notify->MarkShaderComplete();
}
- is_built = true;
- built_condvar.notify_one();
}};
if (thread_worker) {
thread_worker->QueueWork(std::move(func));
@@ -292,7 +285,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
buffer_cache.runtime.SetBaseStorageBindings(base_storage_bindings);
buffer_cache.runtime.SetEnableStorageBuffers(use_storage_buffers);
- const auto& regs{maxwell3d.regs};
+ const auto& regs{maxwell3d->regs};
const bool via_header_index{regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex};
const auto config_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
const Shader::Info& info{stage_infos[stage]};
@@ -306,7 +299,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
++ssbo_index;
}
}
- const auto& cbufs{maxwell3d.state.shader_stages[stage].const_buffers};
+ const auto& cbufs{maxwell3d->state.shader_stages[stage].const_buffers};
const auto read_handle{[&](const auto& desc, u32 index) {
ASSERT(cbufs[desc.cbuf_index].enabled);
const u32 index_offset{index << desc.size_shift};
@@ -319,13 +312,14 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
const u32 second_offset{desc.secondary_cbuf_offset + index_offset};
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].address +
second_offset};
- const u32 lhs_raw{gpu_memory.Read<u32>(addr)};
- const u32 rhs_raw{gpu_memory.Read<u32>(separate_addr)};
+ const u32 lhs_raw{gpu_memory->Read<u32>(addr) << desc.shift_left};
+ const u32 rhs_raw{gpu_memory->Read<u32>(separate_addr)
+ << desc.secondary_shift_left};
const u32 raw{lhs_raw | rhs_raw};
return TexturePair(raw, via_header_index);
}
}
- return TexturePair(gpu_memory.Read<u32>(addr), via_header_index);
+ return TexturePair(gpu_memory->Read<u32>(addr), via_header_index);
}};
const auto add_image{[&](const auto& desc, bool blacklist) LAMBDA_FORCEINLINE {
for (u32 index = 0; index < desc.count; ++index) {
@@ -440,7 +434,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
buffer_cache.UpdateGraphicsBuffers(is_indexed);
buffer_cache.BindHostGeometryBuffers(is_indexed);
- if (!is_built.load(std::memory_order::relaxed)) {
+ if (!IsBuilt()) {
WaitForBuild();
}
const bool use_assembly{assembly_programs[0].handle != 0};
@@ -585,8 +579,26 @@ void GraphicsPipeline::GenerateTransformFeedbackState() {
}
void GraphicsPipeline::WaitForBuild() {
- std::unique_lock lock{built_mutex};
- built_condvar.wait(lock, [this] { return is_built.load(std::memory_order::relaxed); });
+ if (built_fence.handle == 0) {
+ std::unique_lock lock{built_mutex};
+ built_condvar.wait(lock, [this] { return built_fence.handle != 0; });
+ }
+ ASSERT(glClientWaitSync(built_fence.handle, 0, GL_TIMEOUT_IGNORED) != GL_WAIT_FAILED);
+ is_built = true;
+}
+
+bool GraphicsPipeline::IsBuilt() noexcept {
+ if (is_built) {
+ return true;
+ }
+ if (built_fence.handle == 0) {
+ return false;
+ }
+ // Timeout of zero means this is non-blocking
+ const auto sync_status = glClientWaitSync(built_fence.handle, 0, 0);
+ ASSERT(sync_status != GL_WAIT_FAILED);
+ is_built = sync_status != GL_TIMEOUT_EXPIRED;
+ return is_built;
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.h b/src/video_core/renderer_opengl/gl_graphics_pipeline.h
index 4e28d9a42..a0f0e63cb 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.h
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -14,7 +13,6 @@
#include "common/common_types.h"
#include "shader_recompiler/shader_info.h"
#include "video_core/engines/maxwell_3d.h"
-#include "video_core/memory_manager.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_texture_cache.h"
@@ -73,10 +71,9 @@ static_assert(std::is_trivially_constructible_v<GraphicsPipelineKey>);
class GraphicsPipeline {
public:
explicit GraphicsPipeline(const Device& device, TextureCache& texture_cache_,
- BufferCache& buffer_cache_, Tegra::MemoryManager& gpu_memory_,
- Tegra::Engines::Maxwell3D& maxwell3d_,
- ProgramManager& program_manager_, StateTracker& state_tracker_,
- ShaderWorker* thread_worker, VideoCore::ShaderNotify* shader_notify,
+ BufferCache& buffer_cache_, ProgramManager& program_manager_,
+ StateTracker& state_tracker_, ShaderWorker* thread_worker,
+ VideoCore::ShaderNotify* shader_notify,
std::array<std::string, 5> sources,
std::array<std::vector<u32>, 5> sources_spirv,
const std::array<const Shader::Info*, 5>& infos,
@@ -100,9 +97,7 @@ public:
return writes_global_memory;
}
- [[nodiscard]] bool IsBuilt() const noexcept {
- return is_built.load(std::memory_order::relaxed);
- }
+ [[nodiscard]] bool IsBuilt() noexcept;
template <typename Spec>
static auto MakeConfigureSpecFunc() {
@@ -111,6 +106,11 @@ public:
};
}
+ void SetEngine(Tegra::Engines::Maxwell3D* maxwell3d_, Tegra::MemoryManager* gpu_memory_) {
+ maxwell3d = maxwell3d_;
+ gpu_memory = gpu_memory_;
+ }
+
private:
template <typename Spec>
void ConfigureImpl(bool is_indexed);
@@ -123,8 +123,8 @@ private:
TextureCache& texture_cache;
BufferCache& buffer_cache;
- Tegra::MemoryManager& gpu_memory;
- Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::MemoryManager* gpu_memory;
+ Tegra::Engines::Maxwell3D* maxwell3d;
ProgramManager& program_manager;
StateTracker& state_tracker;
const GraphicsPipelineKey key;
@@ -154,7 +154,8 @@ private:
std::mutex built_mutex;
std::condition_variable built_condvar;
- std::atomic_bool is_built{false};
+ OGLSync built_fence{};
+ bool is_built{false};
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp
index acebbf5f4..5070db441 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_query_cache.cpp
@@ -1,17 +1,13 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
-#include <cstring>
#include <memory>
-#include <unordered_map>
#include <utility>
#include <vector>
#include <glad/glad.h>
-#include "common/assert.h"
#include "core/core.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
@@ -30,9 +26,8 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) {
} // Anonymous namespace
-QueryCache::QueryCache(RasterizerOpenGL& rasterizer_, Tegra::Engines::Maxwell3D& maxwell3d_,
- Tegra::MemoryManager& gpu_memory_)
- : QueryCacheBase(rasterizer_, maxwell3d_, gpu_memory_), gl_rasterizer{rasterizer_} {}
+QueryCache::QueryCache(RasterizerOpenGL& rasterizer_)
+ : QueryCacheBase(rasterizer_), gl_rasterizer{rasterizer_} {}
QueryCache::~QueryCache() = default;
diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h
index 7bbe5cfe9..14ce59990 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.h
+++ b/src/video_core/renderer_opengl/gl_query_cache.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -29,8 +28,7 @@ using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
class QueryCache final
: public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> {
public:
- explicit QueryCache(RasterizerOpenGL& rasterizer_, Tegra::Engines::Maxwell3D& maxwell3d_,
- Tegra::MemoryManager& gpu_memory_);
+ explicit QueryCache(RasterizerOpenGL& rasterizer_);
~QueryCache();
OGLQuery AllocateQuery(VideoCore::QueryType type);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 142412a8e..c2d80605d 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -1,14 +1,11 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <bitset>
#include <memory>
-#include <string>
#include <string_view>
-#include <tuple>
#include <utility>
#include <glad/glad.h>
@@ -17,8 +14,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/control/channel_state.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
@@ -58,22 +56,20 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window_, Tegra
Core::Memory::Memory& cpu_memory_, const Device& device_,
ScreenInfo& screen_info_, ProgramManager& program_manager_,
StateTracker& state_tracker_)
- : RasterizerAccelerated(cpu_memory_), gpu(gpu_), maxwell3d(gpu.Maxwell3D()),
- kepler_compute(gpu.KeplerCompute()), gpu_memory(gpu.MemoryManager()), device(device_),
- screen_info(screen_info_), program_manager(program_manager_), state_tracker(state_tracker_),
+ : RasterizerAccelerated(cpu_memory_), gpu(gpu_), device(device_), screen_info(screen_info_),
+ program_manager(program_manager_), state_tracker(state_tracker_),
texture_cache_runtime(device, program_manager, state_tracker),
- texture_cache(texture_cache_runtime, *this, maxwell3d, kepler_compute, gpu_memory),
- buffer_cache_runtime(device),
- buffer_cache(*this, maxwell3d, kepler_compute, gpu_memory, cpu_memory_, buffer_cache_runtime),
- shader_cache(*this, emu_window_, maxwell3d, kepler_compute, gpu_memory, device, texture_cache,
- buffer_cache, program_manager, state_tracker, gpu.ShaderNotify()),
- query_cache(*this, maxwell3d, gpu_memory), accelerate_dma(buffer_cache),
+ texture_cache(texture_cache_runtime, *this), buffer_cache_runtime(device),
+ buffer_cache(*this, cpu_memory_, buffer_cache_runtime),
+ shader_cache(*this, emu_window_, device, texture_cache, buffer_cache, program_manager,
+ state_tracker, gpu.ShaderNotify()),
+ query_cache(*this), accelerate_dma(buffer_cache),
fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache) {}
RasterizerOpenGL::~RasterizerOpenGL() = default;
void RasterizerOpenGL::SyncVertexFormats() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::VertexFormats]) {
return;
}
@@ -91,7 +87,7 @@ void RasterizerOpenGL::SyncVertexFormats() {
}
flags[Dirty::VertexFormat0 + index] = false;
- const auto attrib = maxwell3d.regs.vertex_attrib_format[index];
+ const auto attrib = maxwell3d->regs.vertex_attrib_format[index];
const auto gl_index = static_cast<GLuint>(index);
// Disable constant attributes.
@@ -115,13 +111,13 @@ void RasterizerOpenGL::SyncVertexFormats() {
}
void RasterizerOpenGL::SyncVertexInstances() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::VertexInstances]) {
return;
}
flags[Dirty::VertexInstances] = false;
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
for (std::size_t index = 0; index < NUM_SUPPORTED_VERTEX_ATTRIBUTES; ++index) {
if (!flags[Dirty::VertexInstance0 + index]) {
continue;
@@ -142,11 +138,11 @@ void RasterizerOpenGL::LoadDiskResources(u64 title_id, std::stop_token stop_load
void RasterizerOpenGL::Clear() {
MICROPROFILE_SCOPE(OpenGL_Clears);
- if (!maxwell3d.ShouldExecute()) {
+ if (!maxwell3d->ShouldExecute()) {
return;
}
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
bool use_color{};
bool use_depth{};
bool use_stencil{};
@@ -212,28 +208,33 @@ 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()};
if (!pipeline) {
return;
}
+
+ gpu.TickWork();
+
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+ pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->Configure(is_indexed);
SyncState();
- const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(maxwell3d.regs.draw.topology);
+ const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(maxwell3d->regs.draw.topology);
BeginTransformFeedback(pipeline, primitive_mode);
- const GLuint base_instance = static_cast<GLuint>(maxwell3d.regs.vb_base_instance);
+ const GLuint base_instance = static_cast<GLuint>(maxwell3d->regs.vb_base_instance);
const GLsizei num_instances =
- static_cast<GLsizei>(is_instanced ? maxwell3d.mme_draw.instance_count : 1);
+ static_cast<GLsizei>(is_instanced ? maxwell3d->mme_draw.instance_count : 1);
if (is_indexed) {
- const GLint base_vertex = static_cast<GLint>(maxwell3d.regs.vb_element_base);
- const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d.regs.index_array.count);
+ const GLint base_vertex = static_cast<GLint>(maxwell3d->regs.vb_element_base);
+ const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d->regs.index_array.count);
const GLvoid* const offset = buffer_cache_runtime.IndexOffset();
- const GLenum format = MaxwellToGL::IndexFormat(maxwell3d.regs.index_array.format);
+ const GLenum format = MaxwellToGL::IndexFormat(maxwell3d->regs.index_array.format);
if (num_instances == 1 && base_instance == 0 && base_vertex == 0) {
glDrawElements(primitive_mode, num_vertices, format, offset);
} else if (num_instances == 1 && base_instance == 0) {
@@ -252,8 +253,8 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
base_instance);
}
} else {
- const GLint base_vertex = static_cast<GLint>(maxwell3d.regs.vertex_buffer.first);
- const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d.regs.vertex_buffer.count);
+ const GLint base_vertex = static_cast<GLint>(maxwell3d->regs.vertex_buffer.first);
+ const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d->regs.vertex_buffer.count);
if (num_instances == 1 && base_instance == 0) {
glDrawArrays(primitive_mode, base_vertex, num_vertices);
} else if (base_instance == 0) {
@@ -267,8 +268,6 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
++num_queued_commands;
has_written_global_memory |= pipeline->WritesGlobalMemory();
-
- gpu.TickWork();
}
void RasterizerOpenGL::DispatchCompute() {
@@ -276,8 +275,9 @@ void RasterizerOpenGL::DispatchCompute() {
if (!pipeline) {
return;
}
+ pipeline->SetEngine(kepler_compute, gpu_memory);
pipeline->Configure();
- const auto& qmd{kepler_compute.launch_description};
+ const auto& qmd{kepler_compute->launch_description};
glDispatchCompute(qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z);
++num_queued_commands;
has_written_global_memory |= pipeline->WritesGlobalMemory();
@@ -362,7 +362,7 @@ void RasterizerOpenGL::OnCPUWrite(VAddr addr, u64 size) {
}
}
-void RasterizerOpenGL::SyncGuestHost() {
+void RasterizerOpenGL::InvalidateGPUCache() {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
shader_cache.SyncGuestHost();
{
@@ -383,40 +383,30 @@ void RasterizerOpenGL::UnmapMemory(VAddr addr, u64 size) {
shader_cache.OnCPUWrite(addr, size);
}
-void RasterizerOpenGL::ModifyGPUMemory(GPUVAddr addr, u64 size) {
+void RasterizerOpenGL::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {
{
std::scoped_lock lock{texture_cache.mutex};
- texture_cache.UnmapGPUMemory(addr, size);
+ texture_cache.UnmapGPUMemory(as_id, addr, size);
}
}
-void RasterizerOpenGL::SignalSemaphore(GPUVAddr addr, u32 value) {
- if (!gpu.IsAsync()) {
- gpu_memory.Write<u32>(addr, value);
- return;
- }
- fence_manager.SignalSemaphore(addr, value);
+void RasterizerOpenGL::SignalFence(std::function<void()>&& func) {
+ fence_manager.SignalFence(std::move(func));
+}
+
+void RasterizerOpenGL::SyncOperation(std::function<void()>&& func) {
+ fence_manager.SyncOperation(std::move(func));
}
void RasterizerOpenGL::SignalSyncPoint(u32 value) {
- if (!gpu.IsAsync()) {
- gpu.IncrementSyncPoint(value);
- return;
- }
fence_manager.SignalSyncPoint(value);
}
void RasterizerOpenGL::SignalReference() {
- if (!gpu.IsAsync()) {
- return;
- }
fence_manager.SignalOrdering();
}
void RasterizerOpenGL::ReleaseFences() {
- if (!gpu.IsAsync()) {
- return;
- }
fence_manager.WaitPendingFences();
}
@@ -433,6 +423,7 @@ void RasterizerOpenGL::WaitForIdle() {
}
void RasterizerOpenGL::FragmentBarrier() {
+ glTextureBarrier();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT);
}
@@ -485,13 +476,13 @@ Tegra::Engines::AccelerateDMAInterface& RasterizerOpenGL::AccessAccelerateDMA()
}
void RasterizerOpenGL::AccelerateInlineToMemory(GPUVAddr address, size_t copy_size,
- std::span<u8> memory) {
- auto cpu_addr = gpu_memory.GpuToCpuAddress(address);
+ std::span<const u8> memory) {
+ auto cpu_addr = gpu_memory->GpuToCpuAddress(address);
if (!cpu_addr) [[unlikely]] {
- gpu_memory.WriteBlock(address, memory.data(), copy_size);
+ gpu_memory->WriteBlock(address, memory.data(), copy_size);
return;
}
- gpu_memory.WriteBlockUnsafe(address, memory.data(), copy_size);
+ gpu_memory->WriteBlockUnsafe(address, memory.data(), copy_size);
{
std::unique_lock<std::mutex> lock{buffer_cache.mutex};
if (!buffer_cache.InlineMemory(*cpu_addr, copy_size, memory)) {
@@ -522,6 +513,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;
@@ -552,19 +545,25 @@ void RasterizerOpenGL::SyncState() {
}
void RasterizerOpenGL::SyncViewport() {
- auto& flags = maxwell3d.dirty.flags;
- const auto& regs = maxwell3d.regs;
+ auto& flags = maxwell3d->dirty.flags;
+ const auto& regs = maxwell3d->regs;
const bool rescale_viewports = flags[VideoCommon::Dirty::RescaleViewports];
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);
- if (regs.screen_y_control.triangle_rast_flip != 0 &&
- regs.viewport_transform[0].scale_y < 0.0f) {
+ bool flip_faces = true;
+ if (regs.screen_y_control.triangle_rast_flip != 0) {
+ flip_faces = !flip_faces;
+ }
+ if (regs.viewport_transform[0].scale_y < 0.0f) {
+ flip_faces = !flip_faces;
+ }
+ if (flip_faces) {
switch (mode) {
case GL_CW:
mode = GL_CCW;
@@ -652,23 +651,23 @@ void RasterizerOpenGL::SyncViewport() {
}
void RasterizerOpenGL::SyncDepthClamp() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::DepthClampEnabled]) {
return;
}
flags[Dirty::DepthClampEnabled] = false;
- oglEnable(GL_DEPTH_CLAMP, maxwell3d.regs.view_volume_clip_control.depth_clamp_disabled == 0);
+ oglEnable(GL_DEPTH_CLAMP, maxwell3d->regs.view_volume_clip_control.depth_clamp_disabled == 0);
}
void RasterizerOpenGL::SyncClipEnabled(u32 clip_mask) {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::ClipDistances] && !flags[VideoCommon::Dirty::Shaders]) {
return;
}
flags[Dirty::ClipDistances] = false;
- clip_mask &= maxwell3d.regs.clip_distance_enabled;
+ clip_mask &= maxwell3d->regs.clip_distance_enabled;
if (clip_mask == last_clip_distance_mask) {
return;
}
@@ -684,8 +683,8 @@ void RasterizerOpenGL::SyncClipCoef() {
}
void RasterizerOpenGL::SyncCullMode() {
- auto& flags = maxwell3d.dirty.flags;
- const auto& regs = maxwell3d.regs;
+ auto& flags = maxwell3d->dirty.flags;
+ const auto& regs = maxwell3d->regs;
if (flags[Dirty::CullTest]) {
flags[Dirty::CullTest] = false;
@@ -700,23 +699,23 @@ void RasterizerOpenGL::SyncCullMode() {
}
void RasterizerOpenGL::SyncPrimitiveRestart() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::PrimitiveRestart]) {
return;
}
flags[Dirty::PrimitiveRestart] = false;
- if (maxwell3d.regs.primitive_restart.enabled) {
+ if (maxwell3d->regs.primitive_restart.enabled) {
glEnable(GL_PRIMITIVE_RESTART);
- glPrimitiveRestartIndex(maxwell3d.regs.primitive_restart.index);
+ glPrimitiveRestartIndex(maxwell3d->regs.primitive_restart.index);
} else {
glDisable(GL_PRIMITIVE_RESTART);
}
}
void RasterizerOpenGL::SyncDepthTestState() {
- auto& flags = maxwell3d.dirty.flags;
- const auto& regs = maxwell3d.regs;
+ auto& flags = maxwell3d->dirty.flags;
+ const auto& regs = maxwell3d->regs;
if (flags[Dirty::DepthMask]) {
flags[Dirty::DepthMask] = false;
@@ -735,13 +734,13 @@ void RasterizerOpenGL::SyncDepthTestState() {
}
void RasterizerOpenGL::SyncStencilTestState() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::StencilTest]) {
return;
}
flags[Dirty::StencilTest] = false;
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
oglEnable(GL_STENCIL_TEST, regs.stencil_enable);
glStencilFuncSeparate(GL_FRONT, MaxwellToGL::ComparisonOp(regs.stencil_front_func_func),
@@ -766,23 +765,23 @@ void RasterizerOpenGL::SyncStencilTestState() {
}
void RasterizerOpenGL::SyncRasterizeEnable() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::RasterizeEnable]) {
return;
}
flags[Dirty::RasterizeEnable] = false;
- oglEnable(GL_RASTERIZER_DISCARD, maxwell3d.regs.rasterize_enable == 0);
+ oglEnable(GL_RASTERIZER_DISCARD, maxwell3d->regs.rasterize_enable == 0);
}
void RasterizerOpenGL::SyncPolygonModes() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::PolygonModes]) {
return;
}
flags[Dirty::PolygonModes] = false;
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
if (regs.fill_rectangle) {
if (!GLAD_GL_NV_fill_rectangle) {
LOG_ERROR(Render_OpenGL, "GL_NV_fill_rectangle used and not supported");
@@ -815,7 +814,7 @@ void RasterizerOpenGL::SyncPolygonModes() {
}
void RasterizerOpenGL::SyncColorMask() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::ColorMasks]) {
return;
}
@@ -824,7 +823,7 @@ void RasterizerOpenGL::SyncColorMask() {
const bool force = flags[Dirty::ColorMaskCommon];
flags[Dirty::ColorMaskCommon] = false;
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
if (regs.color_mask_common) {
if (!force && !flags[Dirty::ColorMask0]) {
return;
@@ -849,30 +848,30 @@ void RasterizerOpenGL::SyncColorMask() {
}
void RasterizerOpenGL::SyncMultiSampleState() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::MultisampleControl]) {
return;
}
flags[Dirty::MultisampleControl] = false;
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
oglEnable(GL_SAMPLE_ALPHA_TO_COVERAGE, regs.multisample_control.alpha_to_coverage);
oglEnable(GL_SAMPLE_ALPHA_TO_ONE, regs.multisample_control.alpha_to_one);
}
void RasterizerOpenGL::SyncFragmentColorClampState() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::FragmentClampColor]) {
return;
}
flags[Dirty::FragmentClampColor] = false;
- glClampColor(GL_CLAMP_FRAGMENT_COLOR, maxwell3d.regs.frag_color_clamp ? GL_TRUE : GL_FALSE);
+ glClampColor(GL_CLAMP_FRAGMENT_COLOR, maxwell3d->regs.frag_color_clamp ? GL_TRUE : GL_FALSE);
}
void RasterizerOpenGL::SyncBlendState() {
- auto& flags = maxwell3d.dirty.flags;
- const auto& regs = maxwell3d.regs;
+ auto& flags = maxwell3d->dirty.flags;
+ const auto& regs = maxwell3d->regs;
if (flags[Dirty::BlendColor]) {
flags[Dirty::BlendColor] = false;
@@ -929,13 +928,13 @@ void RasterizerOpenGL::SyncBlendState() {
}
void RasterizerOpenGL::SyncLogicOpState() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::LogicOp]) {
return;
}
flags[Dirty::LogicOp] = false;
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
if (regs.logic_op.enable) {
glEnable(GL_COLOR_LOGIC_OP);
glLogicOp(MaxwellToGL::LogicOp(regs.logic_op.operation));
@@ -945,7 +944,7 @@ void RasterizerOpenGL::SyncLogicOpState() {
}
void RasterizerOpenGL::SyncScissorTest() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::Scissors] && !flags[VideoCommon::Dirty::RescaleScissors]) {
return;
}
@@ -954,7 +953,7 @@ void RasterizerOpenGL::SyncScissorTest() {
const bool force = flags[VideoCommon::Dirty::RescaleScissors];
flags[VideoCommon::Dirty::RescaleScissors] = false;
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
const auto& resolution = Settings::values.resolution_info;
const bool is_rescaling{texture_cache.IsRescaling()};
@@ -990,39 +989,39 @@ void RasterizerOpenGL::SyncScissorTest() {
}
void RasterizerOpenGL::SyncPointState() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::PointSize]) {
return;
}
flags[Dirty::PointSize] = false;
- oglEnable(GL_POINT_SPRITE, maxwell3d.regs.point_sprite_enable);
- oglEnable(GL_PROGRAM_POINT_SIZE, maxwell3d.regs.vp_point_size.enable);
+ oglEnable(GL_POINT_SPRITE, maxwell3d->regs.point_sprite_enable);
+ oglEnable(GL_PROGRAM_POINT_SIZE, maxwell3d->regs.vp_point_size.enable);
const bool is_rescaling{texture_cache.IsRescaling()};
const float scale = is_rescaling ? Settings::values.resolution_info.up_factor : 1.0f;
- glPointSize(std::max(1.0f, maxwell3d.regs.point_size * scale));
+ glPointSize(std::max(1.0f, maxwell3d->regs.point_size * scale));
}
void RasterizerOpenGL::SyncLineState() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::LineWidth]) {
return;
}
flags[Dirty::LineWidth] = false;
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
oglEnable(GL_LINE_SMOOTH, regs.line_smooth_enable);
glLineWidth(regs.line_smooth_enable ? regs.line_width_smooth : regs.line_width_aliased);
}
void RasterizerOpenGL::SyncPolygonOffset() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::PolygonOffset]) {
return;
}
flags[Dirty::PolygonOffset] = false;
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
oglEnable(GL_POLYGON_OFFSET_FILL, regs.polygon_offset_fill_enable);
oglEnable(GL_POLYGON_OFFSET_LINE, regs.polygon_offset_line_enable);
oglEnable(GL_POLYGON_OFFSET_POINT, regs.polygon_offset_point_enable);
@@ -1036,13 +1035,13 @@ void RasterizerOpenGL::SyncPolygonOffset() {
}
void RasterizerOpenGL::SyncAlphaTest() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::AlphaTest]) {
return;
}
flags[Dirty::AlphaTest] = false;
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
if (regs.alpha_test_enabled) {
glEnable(GL_ALPHA_TEST);
glAlphaFunc(MaxwellToGL::ComparisonOp(regs.alpha_test_func), regs.alpha_test_ref);
@@ -1052,17 +1051,17 @@ void RasterizerOpenGL::SyncAlphaTest() {
}
void RasterizerOpenGL::SyncFramebufferSRGB() {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::FramebufferSRGB]) {
return;
}
flags[Dirty::FramebufferSRGB] = false;
- oglEnable(GL_FRAMEBUFFER_SRGB, maxwell3d.regs.framebuffer_srgb);
+ oglEnable(GL_FRAMEBUFFER_SRGB, maxwell3d->regs.framebuffer_srgb);
}
void RasterizerOpenGL::BeginTransformFeedback(GraphicsPipeline* program, GLenum primitive_mode) {
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
if (regs.tfb_enabled == 0) {
return;
}
@@ -1081,11 +1080,48 @@ void RasterizerOpenGL::BeginTransformFeedback(GraphicsPipeline* program, GLenum
}
void RasterizerOpenGL::EndTransformFeedback() {
- if (maxwell3d.regs.tfb_enabled != 0) {
+ if (maxwell3d->regs.tfb_enabled != 0) {
glEndTransformFeedback();
}
}
+void RasterizerOpenGL::InitializeChannel(Tegra::Control::ChannelState& channel) {
+ CreateChannel(channel);
+ {
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+ texture_cache.CreateChannel(channel);
+ buffer_cache.CreateChannel(channel);
+ }
+ shader_cache.CreateChannel(channel);
+ query_cache.CreateChannel(channel);
+ state_tracker.SetupTables(channel);
+}
+
+void RasterizerOpenGL::BindChannel(Tegra::Control::ChannelState& channel) {
+ const s32 channel_id = channel.bind_id;
+ BindToChannel(channel_id);
+ {
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+ texture_cache.BindToChannel(channel_id);
+ buffer_cache.BindToChannel(channel_id);
+ }
+ shader_cache.BindToChannel(channel_id);
+ query_cache.BindToChannel(channel_id);
+ state_tracker.ChangeChannel(channel);
+ state_tracker.InvalidateState();
+}
+
+void RasterizerOpenGL::ReleaseChannel(s32 channel_id) {
+ EraseChannel(channel_id);
+ {
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+ texture_cache.EraseChannel(channel_id);
+ buffer_cache.EraseChannel(channel_id);
+ }
+ shader_cache.EraseChannel(channel_id);
+ query_cache.EraseChannel(channel_id);
+}
+
AccelerateDMA::AccelerateDMA(BufferCache& buffer_cache_) : buffer_cache{buffer_cache_} {}
bool AccelerateDMA::BufferCopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount) {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 98f6fd342..45131b785 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -1,24 +1,18 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
-#include <atomic>
#include <cstddef>
-#include <memory>
#include <optional>
-#include <tuple>
-#include <utility>
#include <boost/container/static_vector.hpp>
#include <glad/glad.h>
#include "common/common_types.h"
-#include "video_core/engines/const_buffer_info.h"
-#include "video_core/engines/maxwell_3d.h"
+#include "video_core/control/channel_state_cache.h"
#include "video_core/engines/maxwell_dma.h"
#include "video_core/rasterizer_accelerated.h"
#include "video_core/rasterizer_interface.h"
@@ -26,12 +20,8 @@
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_fence_manager.h"
#include "video_core/renderer_opengl/gl_query_cache.h"
-#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_cache.h"
-#include "video_core/renderer_opengl/gl_shader_manager.h"
-#include "video_core/renderer_opengl/gl_state_tracker.h"
#include "video_core/renderer_opengl/gl_texture_cache.h"
-#include "video_core/textures/texture.h"
namespace Core::Memory {
class Memory;
@@ -69,7 +59,8 @@ private:
BufferCache& buffer_cache;
};
-class RasterizerOpenGL : public VideoCore::RasterizerAccelerated {
+class RasterizerOpenGL : public VideoCore::RasterizerAccelerated,
+ protected VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
public:
explicit RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
Core::Memory::Memory& cpu_memory_, const Device& device_,
@@ -89,10 +80,11 @@ public:
bool MustFlushRegion(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size) override;
void OnCPUWrite(VAddr addr, u64 size) override;
- void SyncGuestHost() override;
+ void InvalidateGPUCache() override;
void UnmapMemory(VAddr addr, u64 size) override;
- void ModifyGPUMemory(GPUVAddr addr, u64 size) override;
- void SignalSemaphore(GPUVAddr addr, u32 value) override;
+ void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override;
+ void SignalFence(std::function<void()>&& func) override;
+ void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override;
void SignalReference() override;
void ReleaseFences() override;
@@ -107,7 +99,7 @@ public:
const Tegra::Engines::Fermi2D::Config& copy_config) override;
Tegra::Engines::AccelerateDMAInterface& AccessAccelerateDMA() override;
void AccelerateInlineToMemory(GPUVAddr address, size_t copy_size,
- std::span<u8> memory) override;
+ std::span<const u8> memory) override;
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
@@ -118,6 +110,12 @@ public:
return num_queued_commands > 0;
}
+ void InitializeChannel(Tegra::Control::ChannelState& channel) override;
+
+ void BindChannel(Tegra::Control::ChannelState& channel) override;
+
+ void ReleaseChannel(s32 channel_id) override;
+
private:
static constexpr size_t MAX_TEXTURES = 192;
static constexpr size_t MAX_IMAGES = 48;
@@ -202,9 +200,6 @@ private:
void EndTransformFeedback();
Tegra::GPU& gpu;
- Tegra::Engines::Maxwell3D& maxwell3d;
- Tegra::Engines::KeplerCompute& kepler_compute;
- Tegra::MemoryManager& gpu_memory;
const Device& device;
ScreenInfo& screen_info;
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp
index 5e7101d28..3a664fdec 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp
@@ -1,11 +1,8 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
-#include <utility>
#include <glad/glad.h>
-#include "common/common_types.h"
#include "common/microprofile.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_util.h"
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h
index 84e07f8bd..bc05ba4bd 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.h
+++ b/src/video_core/renderer_opengl/gl_resource_manager.h
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index f71e01a34..5a29a41d2 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
#include <fstream>
@@ -14,10 +13,8 @@
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
-#include "common/scope_exit.h"
#include "common/settings.h"
#include "common/thread_worker.h"
-#include "core/core.h"
#include "shader_recompiler/backend/glasm/emit_glasm.h"
#include "shader_recompiler/backend/glsl/emit_glsl.h"
#include "shader_recompiler/backend/spirv/emit_spirv.h"
@@ -29,7 +26,6 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
-#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_cache.h"
#include "video_core/renderer_opengl/gl_shader_util.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
@@ -53,7 +49,7 @@ using VideoCommon::LoadPipelines;
using VideoCommon::SerializePipeline;
using Context = ShaderContext::Context;
-constexpr u32 CACHE_VERSION = 5;
+constexpr u32 CACHE_VERSION = 6;
template <typename Container>
auto MakeSpan(Container& container) {
@@ -89,7 +85,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key,
case Maxwell::TessellationPrimitive::Quads:
return Shader::TessPrimitive::Quads;
}
- UNREACHABLE();
+ ASSERT(false);
return Shader::TessPrimitive::Triangles;
}();
info.tess_spacing = [&] {
@@ -101,7 +97,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key,
case Maxwell::TessellationSpacing::FractionalEven:
return Shader::TessSpacing::FractionalEven;
}
- UNREACHABLE();
+ ASSERT(false);
return Shader::TessSpacing::Equal;
}();
break;
@@ -155,16 +151,13 @@ void SetXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell& regs
} // Anonymous namespace
ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindow& emu_window_,
- Tegra::Engines::Maxwell3D& maxwell3d_,
- Tegra::Engines::KeplerCompute& kepler_compute_,
- Tegra::MemoryManager& gpu_memory_, const Device& device_,
- TextureCache& texture_cache_, BufferCache& buffer_cache_,
- ProgramManager& program_manager_, StateTracker& state_tracker_,
- VideoCore::ShaderNotify& shader_notify_)
- : VideoCommon::ShaderCache{rasterizer_, gpu_memory_, maxwell3d_, kepler_compute_},
- emu_window{emu_window_}, device{device_}, texture_cache{texture_cache_},
- buffer_cache{buffer_cache_}, program_manager{program_manager_}, state_tracker{state_tracker_},
- shader_notify{shader_notify_}, use_asynchronous_shaders{device.UseAsynchronousShaders()},
+ const Device& device_, TextureCache& texture_cache_,
+ BufferCache& buffer_cache_, ProgramManager& program_manager_,
+ StateTracker& state_tracker_, VideoCore::ShaderNotify& shader_notify_)
+ : VideoCommon::ShaderCache{rasterizer_}, emu_window{emu_window_}, device{device_},
+ texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, program_manager{program_manager_},
+ state_tracker{state_tracker_}, shader_notify{shader_notify_},
+ use_asynchronous_shaders{device.UseAsynchronousShaders()},
profile{
.supported_spirv = 0x00010000,
@@ -261,7 +254,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));
}
@@ -283,7 +276,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));
}
@@ -303,7 +296,7 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
state.has_loaded = true;
lock.unlock();
- workers->WaitForRequests();
+ workers->WaitForRequests(stop_loading);
if (!use_asynchronous_shaders) {
workers.reset();
}
@@ -314,7 +307,7 @@ GraphicsPipeline* ShaderCache::CurrentGraphicsPipeline() {
current_pipeline = nullptr;
return nullptr;
}
- const auto& regs{maxwell3d.regs};
+ const auto& regs{maxwell3d->regs};
graphics_key.raw = 0;
graphics_key.early_z.Assign(regs.force_early_fragment_tests != 0 ? 1 : 0);
graphics_key.gs_input_topology.Assign(graphics_key.unique_hashes[4] != 0
@@ -355,13 +348,13 @@ GraphicsPipeline* ShaderCache::BuiltPipeline(GraphicsPipeline* pipeline) const n
}
// If something is using depth, we can assume that games are not rendering anything which
// will be used one time.
- if (maxwell3d.regs.zeta_enable) {
+ if (maxwell3d->regs.zeta_enable) {
return nullptr;
}
// If games are using a small index count, we can assume these are full screen quads.
// Usually these shaders are only used once for building textures so we can assume they
// can't be built async
- if (maxwell3d.regs.index_array.count <= 6 || maxwell3d.regs.vertex_buffer.count <= 6) {
+ if (maxwell3d->regs.index_array.count <= 6 || maxwell3d->regs.vertex_buffer.count <= 6) {
return pipeline;
}
return nullptr;
@@ -372,7 +365,7 @@ ComputePipeline* ShaderCache::CurrentComputePipeline() {
if (!shader) {
return nullptr;
}
- const auto& qmd{kepler_compute.launch_description};
+ const auto& qmd{kepler_compute->launch_description};
const ComputePipelineKey key{
.unique_hash = shader->unique_hash,
.shared_memory_size = qmd.shared_alloc,
@@ -484,9 +477,9 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
previous_program = &program;
}
auto* const thread_worker{build_in_parallel ? workers.get() : nullptr};
- return std::make_unique<GraphicsPipeline>(
- device, texture_cache, buffer_cache, gpu_memory, maxwell3d, program_manager, state_tracker,
- thread_worker, &shader_notify, sources, sources_spirv, infos, key);
+ return std::make_unique<GraphicsPipeline>(device, texture_cache, buffer_cache, program_manager,
+ state_tracker, thread_worker, &shader_notify, sources,
+ sources_spirv, infos, key);
} catch (Shader::Exception& exception) {
LOG_ERROR(Render_OpenGL, "{}", exception.what());
@@ -495,9 +488,9 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
const ComputePipelineKey& key, const VideoCommon::ShaderInfo* shader) {
- const GPUVAddr program_base{kepler_compute.regs.code_loc.Address()};
- const auto& qmd{kepler_compute.launch_description};
- ComputeEnvironment env{kepler_compute, gpu_memory, program_base, qmd.program_start};
+ const GPUVAddr program_base{kepler_compute->regs.code_loc.Address()};
+ const auto& qmd{kepler_compute->launch_description};
+ ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start};
env.SetCachedSize(shader->size_bytes);
main_pools.ReleaseContents();
@@ -540,9 +533,8 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
break;
}
- return std::make_unique<ComputePipeline>(device, texture_cache, buffer_cache, gpu_memory,
- kepler_compute, program_manager, program.info, code,
- code_spirv);
+ return std::make_unique<ComputePipeline>(device, texture_cache, buffer_cache, program_manager,
+ program.info, code, code_spirv);
} catch (Shader::Exception& exception) {
LOG_ERROR(Render_OpenGL, "{}", exception.what());
return nullptr;
@@ -550,7 +542,7 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
std::unique_ptr<ShaderWorker> ShaderCache::CreateWorkers() const {
return std::make_unique<ShaderWorker>(std::max(std::thread::hardware_concurrency(), 2U) - 1,
- "yuzu:ShaderBuilder",
+ "GlShaderBuilder",
[this] { return Context{emu_window}; });
}
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index a34110b37..89f181fe3 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -1,21 +1,15 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <array>
#include <filesystem>
#include <stop_token>
#include <unordered_map>
-#include <glad/glad.h>
-
#include "common/common_types.h"
#include "common/thread_worker.h"
-#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/host_translate_info.h"
-#include "shader_recompiler/object_pool.h"
#include "shader_recompiler/profile.h"
#include "video_core/renderer_opengl/gl_compute_pipeline.h"
#include "video_core/renderer_opengl/gl_graphics_pipeline.h"
@@ -36,12 +30,9 @@ using ShaderWorker = Common::StatefulThreadWorker<ShaderContext::Context>;
class ShaderCache : public VideoCommon::ShaderCache {
public:
explicit ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindow& emu_window_,
- Tegra::Engines::Maxwell3D& maxwell3d_,
- Tegra::Engines::KeplerCompute& kepler_compute_,
- Tegra::MemoryManager& gpu_memory_, const Device& device_,
- TextureCache& texture_cache_, BufferCache& buffer_cache_,
- ProgramManager& program_manager_, StateTracker& state_tracker_,
- VideoCore::ShaderNotify& shader_notify_);
+ const Device& device_, TextureCache& texture_cache_,
+ BufferCache& buffer_cache_, ProgramManager& program_manager_,
+ StateTracker& state_tracker_, VideoCore::ShaderNotify& shader_notify_);
~ShaderCache();
void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
diff --git a/src/video_core/renderer_opengl/gl_shader_context.h b/src/video_core/renderer_opengl/gl_shader_context.h
index 6ff34e5d6..ca2bd8e8e 100644
--- a/src/video_core/renderer_opengl/gl_shader_context.h
+++ b/src/video_core/renderer_opengl/gl_shader_context.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp
index 399959afb..d9c29d8b7 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp
@@ -1,3 +1,2 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h
index d7ef0775d..a84f5aeb3 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.h
+++ b/src/video_core/renderer_opengl/gl_shader_manager.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index d432072ad..a0d9d10ef 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -1,12 +1,10 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
#include <vector>
#include <glad/glad.h>
-#include "common/assert.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "video_core/renderer_opengl/gl_shader_util.h"
@@ -19,6 +17,7 @@ static OGLProgram LinkSeparableProgram(GLuint shader) {
glProgramParameteri(program.handle, GL_PROGRAM_SEPARABLE, GL_TRUE);
glAttachShader(program.handle, shader);
glLinkProgram(program.handle);
+ glDetachShader(program.handle, shader);
if (!Settings::values.renderer_debug) {
return program;
}
diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h
index 4e1a2a8e1..43ebcdeba 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.h
+++ b/src/video_core/renderer_opengl/gl_shader_util.h
@@ -1,18 +1,13 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
-#include <string>
#include <string_view>
-#include <vector>
#include <glad/glad.h>
-#include "common/assert.h"
-#include "common/logging/log.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
namespace OpenGL {
diff --git a/src/video_core/renderer_opengl/gl_state_tracker.cpp b/src/video_core/renderer_opengl/gl_state_tracker.cpp
index 586da84e3..a8f3a0f57 100644
--- a/src/video_core/renderer_opengl/gl_state_tracker.cpp
+++ b/src/video_core/renderer_opengl/gl_state_tracker.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -8,8 +7,8 @@
#include "common/common_types.h"
#include "core/core.h"
+#include "video_core/control/channel_state.h"
#include "video_core/engines/maxwell_3d.h"
-#include "video_core/gpu.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
#define OFF(field_name) MAXWELL3D_REG_INDEX(field_name)
@@ -203,9 +202,8 @@ void SetupDirtyMisc(Tables& tables) {
} // Anonymous namespace
-StateTracker::StateTracker(Tegra::GPU& gpu) : flags{gpu.Maxwell3D().dirty.flags} {
- auto& dirty = gpu.Maxwell3D().dirty;
- auto& tables = dirty.tables;
+void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) {
+ auto& tables{channel_state.maxwell_3d->dirty.tables};
SetupDirtyFlags(tables);
SetupDirtyColorMasks(tables);
SetupDirtyViewports(tables);
@@ -231,4 +229,14 @@ StateTracker::StateTracker(Tegra::GPU& gpu) : flags{gpu.Maxwell3D().dirty.flags}
SetupDirtyMisc(tables);
}
+void StateTracker::ChangeChannel(Tegra::Control::ChannelState& channel_state) {
+ flags = &channel_state.maxwell_3d->dirty.flags;
+}
+
+void StateTracker::InvalidateState() {
+ flags->set();
+}
+
+StateTracker::StateTracker() : flags{&default_flags} {}
+
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_state_tracker.h b/src/video_core/renderer_opengl/gl_state_tracker.h
index 5864c7c07..19bcf3f35 100644
--- a/src/video_core/renderer_opengl/gl_state_tracker.h
+++ b/src/video_core/renderer_opengl/gl_state_tracker.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -9,13 +8,14 @@
#include <glad/glad.h>
#include "common/common_types.h"
-#include "core/core.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h"
namespace Tegra {
-class GPU;
+namespace Control {
+struct ChannelState;
}
+} // namespace Tegra
namespace OpenGL {
@@ -85,7 +85,7 @@ static_assert(Last <= std::numeric_limits<u8>::max());
class StateTracker {
public:
- explicit StateTracker(Tegra::GPU& gpu);
+ explicit StateTracker();
void BindIndexBuffer(GLuint new_index_buffer) {
if (index_buffer == new_index_buffer) {
@@ -123,94 +123,107 @@ public:
}
void NotifyScreenDrawVertexArray() {
- flags[OpenGL::Dirty::VertexFormats] = true;
- flags[OpenGL::Dirty::VertexFormat0 + 0] = true;
- flags[OpenGL::Dirty::VertexFormat0 + 1] = true;
+ (*flags)[OpenGL::Dirty::VertexFormats] = true;
+ (*flags)[OpenGL::Dirty::VertexFormat0 + 0] = true;
+ (*flags)[OpenGL::Dirty::VertexFormat0 + 1] = true;
- flags[VideoCommon::Dirty::VertexBuffers] = true;
- flags[VideoCommon::Dirty::VertexBuffer0] = true;
+ (*flags)[VideoCommon::Dirty::VertexBuffers] = true;
+ (*flags)[VideoCommon::Dirty::VertexBuffer0] = true;
- flags[OpenGL::Dirty::VertexInstances] = true;
- flags[OpenGL::Dirty::VertexInstance0 + 0] = true;
- flags[OpenGL::Dirty::VertexInstance0 + 1] = true;
+ (*flags)[OpenGL::Dirty::VertexInstances] = true;
+ (*flags)[OpenGL::Dirty::VertexInstance0 + 0] = true;
+ (*flags)[OpenGL::Dirty::VertexInstance0 + 1] = true;
}
void NotifyPolygonModes() {
- flags[OpenGL::Dirty::PolygonModes] = true;
- flags[OpenGL::Dirty::PolygonModeFront] = true;
- flags[OpenGL::Dirty::PolygonModeBack] = true;
+ (*flags)[OpenGL::Dirty::PolygonModes] = true;
+ (*flags)[OpenGL::Dirty::PolygonModeFront] = true;
+ (*flags)[OpenGL::Dirty::PolygonModeBack] = true;
}
void NotifyViewport0() {
- flags[OpenGL::Dirty::Viewports] = true;
- flags[OpenGL::Dirty::Viewport0] = true;
+ (*flags)[OpenGL::Dirty::Viewports] = true;
+ (*flags)[OpenGL::Dirty::Viewport0] = true;
}
void NotifyScissor0() {
- flags[OpenGL::Dirty::Scissors] = true;
- flags[OpenGL::Dirty::Scissor0] = true;
+ (*flags)[OpenGL::Dirty::Scissors] = true;
+ (*flags)[OpenGL::Dirty::Scissor0] = true;
}
void NotifyColorMask(size_t index) {
- flags[OpenGL::Dirty::ColorMasks] = true;
- flags[OpenGL::Dirty::ColorMask0 + index] = true;
+ (*flags)[OpenGL::Dirty::ColorMasks] = true;
+ (*flags)[OpenGL::Dirty::ColorMask0 + index] = true;
}
void NotifyBlend0() {
- flags[OpenGL::Dirty::BlendStates] = true;
- flags[OpenGL::Dirty::BlendState0] = true;
+ (*flags)[OpenGL::Dirty::BlendStates] = true;
+ (*flags)[OpenGL::Dirty::BlendState0] = true;
}
void NotifyFramebuffer() {
- flags[VideoCommon::Dirty::RenderTargets] = true;
+ (*flags)[VideoCommon::Dirty::RenderTargets] = true;
}
void NotifyFrontFace() {
- flags[OpenGL::Dirty::FrontFace] = true;
+ (*flags)[OpenGL::Dirty::FrontFace] = true;
}
void NotifyCullTest() {
- flags[OpenGL::Dirty::CullTest] = true;
+ (*flags)[OpenGL::Dirty::CullTest] = true;
}
void NotifyDepthMask() {
- flags[OpenGL::Dirty::DepthMask] = true;
+ (*flags)[OpenGL::Dirty::DepthMask] = true;
}
void NotifyDepthTest() {
- flags[OpenGL::Dirty::DepthTest] = true;
+ (*flags)[OpenGL::Dirty::DepthTest] = true;
}
void NotifyStencilTest() {
- flags[OpenGL::Dirty::StencilTest] = true;
+ (*flags)[OpenGL::Dirty::StencilTest] = true;
}
void NotifyPolygonOffset() {
- flags[OpenGL::Dirty::PolygonOffset] = true;
+ (*flags)[OpenGL::Dirty::PolygonOffset] = true;
}
void NotifyRasterizeEnable() {
- flags[OpenGL::Dirty::RasterizeEnable] = true;
+ (*flags)[OpenGL::Dirty::RasterizeEnable] = true;
}
void NotifyFramebufferSRGB() {
- flags[OpenGL::Dirty::FramebufferSRGB] = true;
+ (*flags)[OpenGL::Dirty::FramebufferSRGB] = true;
}
void NotifyLogicOp() {
- flags[OpenGL::Dirty::LogicOp] = true;
+ (*flags)[OpenGL::Dirty::LogicOp] = true;
}
void NotifyClipControl() {
- flags[OpenGL::Dirty::ClipControl] = true;
+ (*flags)[OpenGL::Dirty::ClipControl] = true;
}
void NotifyAlphaTest() {
- flags[OpenGL::Dirty::AlphaTest] = true;
+ (*flags)[OpenGL::Dirty::AlphaTest] = true;
}
+ void NotifyRange(u8 start, u8 end) {
+ for (auto flag = start; flag <= end; flag++) {
+ (*flags)[flag] = true;
+ }
+ }
+
+ void SetupTables(Tegra::Control::ChannelState& channel_state);
+
+ void ChangeChannel(Tegra::Control::ChannelState& channel_state);
+
+ void InvalidateState();
+
private:
- Tegra::Engines::Maxwell3D::DirtyState::Flags& flags;
+ Tegra::Engines::Maxwell3D::DirtyState::Flags* flags;
+ Tegra::Engines::Maxwell3D::DirtyState::Flags default_flags{};
GLuint framebuffer = 0;
GLuint index_buffer = 0;
diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp
index 77b3ee0fe..2005c8993 100644
--- a/src/video_core/renderer_opengl/gl_stream_buffer.cpp
+++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <memory>
diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h
index 2e67922a6..8fe927aaf 100644
--- a/src/video_core/renderer_opengl/gl_stream_buffer.h
+++ b/src/video_core/renderer_opengl/gl_stream_buffer.h
@@ -1,11 +1,9 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
-#include <memory>
#include <span>
#include <utility>
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 3c1f79a27..99cd11d1e 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -84,7 +83,7 @@ GLenum ImageTarget(const VideoCommon::ImageInfo& info) {
case ImageType::Buffer:
return GL_TEXTURE_BUFFER;
}
- UNREACHABLE_MSG("Invalid image type={}", info.type);
+ ASSERT_MSG(false, "Invalid image type={}", info.type);
return GL_NONE;
}
@@ -94,6 +93,7 @@ GLenum ImageTarget(Shader::TextureType type, int num_samples = 1) {
case Shader::TextureType::Color1D:
return GL_TEXTURE_1D;
case Shader::TextureType::Color2D:
+ case Shader::TextureType::Color2DRect:
return is_multisampled ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
case Shader::TextureType::ColorCube:
return GL_TEXTURE_CUBE_MAP;
@@ -108,7 +108,7 @@ GLenum ImageTarget(Shader::TextureType type, int num_samples = 1) {
case Shader::TextureType::Buffer:
return GL_TEXTURE_BUFFER;
}
- UNREACHABLE_MSG("Invalid image view type={}", type);
+ ASSERT_MSG(false, "Invalid image view type={}", type);
return GL_NONE;
}
@@ -120,7 +120,7 @@ GLenum TextureMode(PixelFormat format, bool is_first) {
case PixelFormat::S8_UINT_D24_UNORM:
return is_first ? GL_STENCIL_INDEX : GL_DEPTH_COMPONENT;
default:
- UNREACHABLE();
+ ASSERT(false);
return GL_DEPTH_COMPONENT;
}
}
@@ -141,7 +141,7 @@ GLint Swizzle(SwizzleSource source) {
case SwizzleSource::OneFloat:
return GL_ONE;
}
- UNREACHABLE_MSG("Invalid swizzle source={}", source);
+ ASSERT_MSG(false, "Invalid swizzle source={}", source);
return GL_NONE;
}
@@ -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;
+ }
+ ASSERT_MSG(false, "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;
}
@@ -356,10 +382,10 @@ OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_form
glTextureStorage3D(handle, gl_num_levels, gl_internal_format, width, height, depth);
break;
case GL_TEXTURE_BUFFER:
- UNREACHABLE();
+ ASSERT(false);
break;
default:
- UNREACHABLE_MSG("Invalid target=0x{:x}", target);
+ ASSERT_MSG(false, "Invalid target=0x{:x}", target);
break;
}
return texture;
@@ -395,7 +421,7 @@ OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_form
case Shader::ImageFormat::R32G32B32A32_UINT:
return GL_RGBA32UI;
}
- UNREACHABLE_MSG("Invalid image format={}", format);
+ ASSERT_MSG(false, "Invalid image format={}", format);
return GL_R32UI;
}
@@ -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];
@@ -477,6 +503,7 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, ProgramManager&
set_view(Shader::TextureType::ColorArray1D, null_image_1d_array.handle);
set_view(Shader::TextureType::ColorArray2D, null_image_view_2d_array.handle);
set_view(Shader::TextureType::ColorArrayCube, null_image_cube_array.handle);
+ set_view(Shader::TextureType::Color2DRect, null_image_view_2d.handle);
if (resolution.active) {
for (size_t i = 0; i < rescale_draw_fbos.size(); ++i) {
@@ -484,6 +511,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 +534,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,
@@ -549,7 +581,7 @@ void TextureCacheRuntime::EmulateCopyImage(Image& dst, Image& src,
} else if (IsPixelFormatBGR(dst.info.format) || IsPixelFormatBGR(src.info.format)) {
format_conversion_pass.ConvertImage(dst, src, copies);
} else {
- UNREACHABLE();
+ ASSERT(false);
}
}
@@ -590,7 +622,7 @@ void TextureCacheRuntime::AccelerateImageUpload(Image& image, const ImageBufferM
case ImageType::Linear:
return util_shaders.PitchUpload(image, map, swizzles);
default:
- UNREACHABLE();
+ ASSERT(false);
break;
}
}
@@ -609,7 +641,7 @@ FormatProperties TextureCacheRuntime::FormatInfo(ImageType type, GLenum internal
case ImageType::e3D:
return format_properties[2].at(internal_format);
default:
- UNREACHABLE();
+ ASSERT(false);
return FormatProperties{};
}
}
@@ -686,6 +718,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;
@@ -857,7 +890,7 @@ void Image::CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t b
}
break;
default:
- UNREACHABLE();
+ ASSERT(false);
}
}
@@ -893,7 +926,7 @@ void Image::CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t b
depth = copy.image_extent.depth;
break;
default:
- UNREACHABLE();
+ ASSERT(false);
}
// Compressed formats don't have a pixel format or type
const bool is_compressed = gl_format == GL_NONE;
@@ -919,7 +952,7 @@ void Image::Scale(bool up_scale) {
case SurfaceType::DepthStencil:
return GL_DEPTH_STENCIL_ATTACHMENT;
default:
- UNREACHABLE();
+ ASSERT(false);
return GL_COLOR_ATTACHMENT0;
}
}();
@@ -934,7 +967,7 @@ void Image::Scale(bool up_scale) {
case SurfaceType::DepthStencil:
return GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
default:
- UNREACHABLE();
+ ASSERT(false);
return GL_COLOR_BUFFER_BIT;
}
}();
@@ -949,7 +982,7 @@ void Image::Scale(bool up_scale) {
case SurfaceType::DepthStencil:
return 3;
default:
- UNREACHABLE();
+ ASSERT(false);
return 0;
}
}();
@@ -1014,7 +1047,7 @@ bool Image::ScaleUp(bool ignore) {
return false;
}
if (info.type == ImageType::Linear) {
- UNREACHABLE();
+ ASSERT(false);
return false;
}
flags |= ImageFlagBits::Rescaled;
@@ -1079,6 +1112,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
flat_range.extent.layers = 1;
[[fallthrough]];
case ImageViewType::e2D:
+ case ImageViewType::Rect:
if (True(flags & VideoCommon::ImageViewFlagBits::Slice)) {
// 2D and 2D array views on a 3D textures are used exclusively for render targets
ASSERT(info.range.extent.levels == 1);
@@ -1104,11 +1138,8 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
SetupView(Shader::TextureType::ColorCube);
SetupView(Shader::TextureType::ColorArrayCube);
break;
- case ImageViewType::Rect:
- UNIMPLEMENTED();
- break;
case ImageViewType::Buffer:
- UNREACHABLE();
+ ASSERT(false);
break;
}
switch (info.type) {
@@ -1119,6 +1150,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
default_handle = Handle(Shader::TextureType::ColorArray1D);
break;
case ImageViewType::e2D:
+ case ImageViewType::Rect:
default_handle = Handle(Shader::TextureType::Color2D);
break;
case ImageViewType::e2DArray:
@@ -1179,6 +1211,7 @@ GLuint ImageView::MakeView(Shader::TextureType view_type, GLenum view_format) {
case Shader::TextureType::Color1D:
case Shader::TextureType::Color2D:
case Shader::TextureType::ColorCube:
+ case Shader::TextureType::Color2DRect:
view_range = flat_range;
break;
case Shader::TextureType::ColorArray1D:
@@ -1219,7 +1252,6 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const TSCEntry& config) {
const GLint seamless = config.cubemap_interface_filtering ? GL_TRUE : GL_FALSE;
UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1);
- UNIMPLEMENTED_IF(config.float_coord_normalization != 0);
sampler.Create();
const GLuint handle = sampler.handle;
@@ -1288,7 +1320,7 @@ Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM
buffer_bits |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
break;
default:
- UNREACHABLE();
+ ASSERT(false);
buffer_bits |= GL_DEPTH_BUFFER_BIT;
break;
}
@@ -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..113528e9b 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,6 +9,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 +21,6 @@ struct ResolutionScalingInfo;
namespace OpenGL {
-class Device;
class ProgramManager;
class StateTracker;
@@ -55,13 +54,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 +83,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 +180,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/gl_texture_cache_base.cpp b/src/video_core/renderer_opengl/gl_texture_cache_base.cpp
index 385358fea..64416fd51 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache_base.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache_base.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "video_core/renderer_opengl/gl_texture_cache.h"
#include "video_core/texture_cache/texture_cache.h"
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index daba42ed9..004421236 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -30,6 +29,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 +87,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}, // G4R4_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
@@ -97,6 +98,9 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR}, // ASTC_2D_10X8_SRGB
{GL_COMPRESSED_RGBA_ASTC_6x6_KHR}, // ASTC_2D_6X6_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}, // ASTC_2D_6X6_SRGB
+ {GL_COMPRESSED_RGBA_ASTC_10x6_KHR}, // ASTC_2D_10X6_UNORM
+ {GL_COMPRESSED_RGBA_ASTC_10x5_KHR}, // ASTC_2D_10X5_UNORM
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR}, // ASTC_2D_10X5_SRGB
{GL_COMPRESSED_RGBA_ASTC_10x10_KHR}, // ASTC_2D_10X10_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}, // ASTC_2D_10X10_SRGB
{GL_COMPRESSED_RGBA_ASTC_12x12_KHR}, // ASTC_2D_12X12_UNORM
@@ -184,6 +188,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) {
case Maxwell::VertexAttribute::Size::Size_32_32_32:
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
return GL_FLOAT;
+ case Maxwell::VertexAttribute::Size::Size_11_11_10:
+ return GL_UNSIGNED_INT_10F_11F_11F_REV;
default:
break;
}
@@ -203,7 +209,7 @@ inline GLenum IndexFormat(Maxwell::IndexFormat index_format) {
case Maxwell::IndexFormat::UnsignedInt:
return GL_UNSIGNED_INT;
}
- UNREACHABLE_MSG("Invalid index_format={}", index_format);
+ ASSERT_MSG(false, "Invalid index_format={}", index_format);
return {};
}
@@ -240,7 +246,7 @@ inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
case Maxwell::PrimitiveTopology::Patches:
return GL_PATCHES;
}
- UNREACHABLE_MSG("Invalid topology={}", topology);
+ ASSERT_MSG(false, "Invalid topology={}", topology);
return GL_POINTS;
}
@@ -268,8 +274,8 @@ inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode,
}
break;
}
- UNREACHABLE_MSG("Invalid texture filter mode={} and mipmap filter mode={}", filter_mode,
- mipmap_filter_mode);
+ ASSERT_MSG(false, "Invalid texture filter mode={} and mipmap filter mode={}", filter_mode,
+ mipmap_filter_mode);
return GL_NEAREST;
}
@@ -547,7 +553,7 @@ inline GLenum PolygonMode(Maxwell::PolygonMode polygon_mode) {
case Maxwell::PolygonMode::Fill:
return GL_FILL;
}
- UNREACHABLE_MSG("Invalid polygon mode={}", polygon_mode);
+ ASSERT_MSG(false, "Invalid polygon mode={}", polygon_mode);
return GL_FILL;
}
@@ -560,7 +566,7 @@ inline GLenum ReductionFilter(Tegra::Texture::SamplerReduction filter) {
case Tegra::Texture::SamplerReduction::Max:
return GL_MAX;
}
- UNREACHABLE_MSG("Invalid reduction filter={}", static_cast<int>(filter));
+ ASSERT_MSG(false, "Invalid reduction filter={}", static_cast<int>(filter));
return GL_WEIGHTED_AVERAGE_ARB;
}
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index f81c1b233..8bd5eba7e 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -1,11 +1,9 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstddef>
#include <cstdlib>
-#include <cstring>
#include <memory>
#include <glad/glad.h>
@@ -15,11 +13,9 @@
#include "common/microprofile.h"
#include "common/settings.h"
#include "common/telemetry.h"
-#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "core/memory.h"
-#include "core/perf_stats.h"
#include "core/telemetry_session.h"
#include "video_core/host_shaders/fxaa_frag.h"
#include "video_core/host_shaders/fxaa_vert.h"
@@ -82,7 +78,7 @@ const char* GetSource(GLenum source) {
case GL_DEBUG_SOURCE_OTHER:
return "OTHER";
default:
- UNREACHABLE();
+ ASSERT(false);
return "Unknown source";
}
}
@@ -104,7 +100,7 @@ const char* GetType(GLenum type) {
case GL_DEBUG_TYPE_MARKER:
return "MARKER";
default:
- UNREACHABLE();
+ ASSERT(false);
return "Unknown type";
}
}
@@ -135,7 +131,7 @@ RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
std::unique_ptr<Core::Frontend::GraphicsContext> context_)
: RendererBase{emu_window_, std::move(context_)}, telemetry_session{telemetry_session_},
- emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, state_tracker{gpu},
+ emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, state_tracker{},
program_manager{device},
rasterizer(emu_window, gpu, cpu_memory, device, screen_info, program_manager, state_tracker) {
if (Settings::values.renderer_debug && GLAD_GL_KHR_debug) {
@@ -211,6 +207,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 =
@@ -326,12 +324,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;
@@ -467,8 +465,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,12 +478,18 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
}
}
- ASSERT_MSG(framebuffer_crop_rect.top == 0, "Unimplemented");
ASSERT_MSG(framebuffer_crop_rect.left == 0, "Unimplemented");
+ f32 left_start{};
+ if (framebuffer_crop_rect.Top() > 0) {
+ left_start = static_cast<f32>(framebuffer_crop_rect.Top()) /
+ static_cast<f32>(framebuffer_crop_rect.Bottom());
+ }
+ 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);
@@ -502,10 +506,14 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
const auto& screen = layout.screen;
const std::array vertices = {
- ScreenRectVertex(screen.left, screen.top, texcoords.top * scale_u, left * scale_v),
- ScreenRectVertex(screen.right, screen.top, texcoords.bottom * scale_u, left * scale_v),
- ScreenRectVertex(screen.left, screen.bottom, texcoords.top * scale_u, right * scale_v),
- ScreenRectVertex(screen.right, screen.bottom, texcoords.bottom * scale_u, right * scale_v),
+ ScreenRectVertex(screen.left, screen.top, texcoords.top * scale_u,
+ left_start + left * scale_v),
+ ScreenRectVertex(screen.right, screen.top, texcoords.bottom * scale_u,
+ left_start + left * scale_v),
+ ScreenRectVertex(screen.left, screen.bottom, texcoords.top * scale_u,
+ left_start + right * scale_v),
+ ScreenRectVertex(screen.right, screen.bottom, texcoords.bottom * scale_u,
+ left_start + right * scale_v),
};
glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices), std::data(vertices));
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index cda333cad..1a32e739d 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,10 +7,12 @@
#include <glad/glad.h>
#include "common/common_types.h"
#include "common/math_util.h"
+
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
namespace Core {
@@ -44,7 +45,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
@@ -133,8 +134,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..404def62e 100644
--- a/src/video_core/renderer_opengl/util_shaders.cpp
+++ b/src/video_core/renderer_opengl/util_shaders.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <span>
#include <string_view>
@@ -13,6 +12,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 +50,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 +249,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:
@@ -261,7 +282,7 @@ GLenum StoreFormat(u32 bytes_per_block) {
case 16:
return GL_RGBA32UI;
}
- UNREACHABLE();
+ ASSERT(false);
return GL_R8UI;
}
diff --git a/src/video_core/renderer_opengl/util_shaders.h b/src/video_core/renderer_opengl/util_shaders.h
index 5de95ea7a..44efb6ecf 100644
--- a/src/video_core/renderer_opengl/util_shaders.h
+++ b/src/video_core/renderer_opengl/util_shaders.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -39,6 +38,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 +50,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 2c3914459..3f2b139e0 100644
--- a/src/video_core/renderer_vulkan/blit_image.cpp
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
@@ -9,6 +8,7 @@
#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h"
#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
+#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
#include "video_core/host_shaders/full_screen_triangle_vert_spv.h"
#include "video_core/host_shaders/vulkan_blit_color_float_frag_spv.h"
#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h"
@@ -349,7 +349,7 @@ VkExtent2D GetConversionExtent(const ImageView& src_image_view) {
}
} // Anonymous namespace
-BlitImageHelper::BlitImageHelper(const Device& device_, VKScheduler& scheduler_,
+BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
StateTracker& state_tracker_, DescriptorPool& descriptor_pool)
: device{device_}, scheduler{scheduler_}, state_tracker{state_tracker_},
one_texture_set_layout(device.GetLogical().CreateDescriptorSetLayout(
@@ -366,16 +366,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;
@@ -474,6 +472,13 @@ void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer,
ConvertDepthStencil(*convert_d24s8_to_abgr8_pipeline, dst_framebuffer, src_image_view);
}
+void BlitImageHelper::ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer,
+ ImageView& src_image_view) {
+ ConvertPipelineColorTargetEx(convert_s8d24_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
+ convert_s8d24_to_abgr8_frag);
+ ConvertDepthStencil(*convert_s8d24_to_abgr8_pipeline, dst_framebuffer, src_image_view);
+}
+
void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
const ImageView& src_image_view) {
const VkPipelineLayout layout = *one_texture_pipeline_layout;
diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h
index 85e7dca5b..5df679fb4 100644
--- a/src/video_core/renderer_vulkan/blit_image.h
+++ b/src/video_core/renderer_vulkan/blit_image.h
@@ -1,11 +1,8 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <compare>
-
#include "video_core/engines/fermi_2d.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/texture_cache/types.h"
@@ -19,7 +16,7 @@ class Device;
class Framebuffer;
class ImageView;
class StateTracker;
-class VKScheduler;
+class Scheduler;
struct BlitImagePipelineKey {
constexpr auto operator<=>(const BlitImagePipelineKey&) const noexcept = default;
@@ -30,7 +27,7 @@ struct BlitImagePipelineKey {
class BlitImageHelper {
public:
- explicit BlitImageHelper(const Device& device, VKScheduler& scheduler,
+ explicit BlitImageHelper(const Device& device, Scheduler& scheduler,
StateTracker& state_tracker, DescriptorPool& descriptor_pool);
~BlitImageHelper();
@@ -56,6 +53,8 @@ public:
void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
+ void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
+
private:
void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
const ImageView& src_image_view);
@@ -83,7 +82,7 @@ private:
vk::ShaderModule& module);
const Device& device;
- VKScheduler& scheduler;
+ Scheduler& scheduler;
StateTracker& state_tracker;
vk::DescriptorSetLayout one_texture_set_layout;
@@ -99,6 +98,7 @@ private:
vk::ShaderModule convert_float_to_depth_frag;
vk::ShaderModule convert_abgr8_to_d24s8_frag;
vk::ShaderModule convert_d24s8_to_abgr8_frag;
+ vk::ShaderModule convert_s8d24_to_abgr8_frag;
vk::Sampler linear_sampler;
vk::Sampler nearest_sampler;
@@ -112,6 +112,7 @@ private:
vk::Pipeline convert_r16_to_d16_pipeline;
vk::Pipeline convert_abgr8_to_d24s8_pipeline;
vk::Pipeline convert_d24s8_to_abgr8_pipeline;
+ vk::Pipeline convert_s8d24_to_abgr8_pipeline;
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
index d70153df3..733b454de 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
@@ -1,12 +1,8 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
-#include <tuple>
-
-#include <boost/functional/hash.hpp>
#include "common/bit_cast.h"
#include "common/cityhash.h"
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
index c9be37935..9d60756e5 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 751e4792b..e7104d377 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <iterator>
@@ -26,7 +25,7 @@ VkFilter Filter(Tegra::Texture::TextureFilter filter) {
case Tegra::Texture::TextureFilter::Linear:
return VK_FILTER_LINEAR;
}
- UNREACHABLE_MSG("Invalid sampler filter={}", filter);
+ ASSERT_MSG(false, "Invalid sampler filter={}", filter);
return {};
}
@@ -43,7 +42,7 @@ VkSamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter
case Tegra::Texture::TextureMipmapFilter::Linear:
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
}
- UNREACHABLE_MSG("Invalid sampler mipmap mode={}", mipmap_filter);
+ ASSERT_MSG(false, "Invalid sampler mipmap mode={}", mipmap_filter);
return {};
}
@@ -71,7 +70,7 @@ VkSamplerAddressMode WrapMode(const Device& device, Tegra::Texture::WrapMode wra
case Tegra::Texture::TextureFilter::Linear:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
}
- UNREACHABLE();
+ ASSERT(false);
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
case Tegra::Texture::WrapMode::MirrorOnceClampToEdge:
return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
@@ -127,6 +126,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
@@ -172,7 +172,7 @@ struct FormatTuple {
{VK_FORMAT_R8G8_SINT, Attachable | Storage}, // R8G8_SINT
{VK_FORMAT_R8G8_UINT, Attachable | Storage}, // R8G8_UINT
{VK_FORMAT_R32G32_UINT, Attachable | Storage}, // R32G32_UINT
- {VK_FORMAT_UNDEFINED}, // R16G16B16X16_FLOAT
+ {VK_FORMAT_R16G16B16A16_SFLOAT, Attachable | Storage}, // R16G16B16X16_FLOAT
{VK_FORMAT_R32_UINT, Attachable | Storage}, // R32_UINT
{VK_FORMAT_R32_SINT, Attachable | Storage}, // R32_SINT
{VK_FORMAT_ASTC_8x8_UNORM_BLOCK}, // ASTC_2D_8X8_UNORM
@@ -184,6 +184,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}, // G4R4_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
@@ -194,6 +195,9 @@ struct FormatTuple {
{VK_FORMAT_ASTC_10x8_SRGB_BLOCK}, // ASTC_2D_10X8_SRGB
{VK_FORMAT_ASTC_6x6_UNORM_BLOCK}, // ASTC_2D_6X6_UNORM
{VK_FORMAT_ASTC_6x6_SRGB_BLOCK}, // ASTC_2D_6X6_SRGB
+ {VK_FORMAT_ASTC_10x6_UNORM_BLOCK}, // ASTC_2D_10X6_UNORM
+ {VK_FORMAT_ASTC_10x5_UNORM_BLOCK}, // ASTC_2D_10X5_UNORM
+ {VK_FORMAT_ASTC_10x5_SRGB_BLOCK}, // ASTC_2D_10X5_SRGB
{VK_FORMAT_ASTC_10x10_UNORM_BLOCK}, // ASTC_2D_10X10_UNORM
{VK_FORMAT_ASTC_10x10_SRGB_BLOCK}, // ASTC_2D_10X10_SRGB
{VK_FORMAT_ASTC_12x12_UNORM_BLOCK}, // ASTC_2D_12X12_UNORM
@@ -315,193 +319,204 @@ VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const Device& device,
}
}
-VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) {
- switch (type) {
- case Maxwell::VertexAttribute::Type::UnsignedNorm:
- switch (size) {
- case Maxwell::VertexAttribute::Size::Size_8:
- return VK_FORMAT_R8_UNORM;
- case Maxwell::VertexAttribute::Size::Size_8_8:
- return VK_FORMAT_R8G8_UNORM;
- case Maxwell::VertexAttribute::Size::Size_8_8_8:
- return VK_FORMAT_R8G8B8_UNORM;
- case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
- return VK_FORMAT_R8G8B8A8_UNORM;
- case Maxwell::VertexAttribute::Size::Size_16:
- return VK_FORMAT_R16_UNORM;
- case Maxwell::VertexAttribute::Size::Size_16_16:
- return VK_FORMAT_R16G16_UNORM;
- case Maxwell::VertexAttribute::Size::Size_16_16_16:
- return VK_FORMAT_R16G16B16_UNORM;
- case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return VK_FORMAT_R16G16B16A16_UNORM;
- case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
- return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
- default:
+VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
+ Maxwell::VertexAttribute::Size size) {
+ const VkFormat format{([&]() {
+ switch (type) {
+ case Maxwell::VertexAttribute::Type::UnsignedNorm:
+ switch (size) {
+ case Maxwell::VertexAttribute::Size::Size_8:
+ return VK_FORMAT_R8_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_8_8:
+ return VK_FORMAT_R8G8_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8:
+ return VK_FORMAT_R8G8B8_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
+ return VK_FORMAT_R8G8B8A8_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_16:
+ return VK_FORMAT_R16_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_16_16:
+ return VK_FORMAT_R16G16_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16:
+ return VK_FORMAT_R16G16B16_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
+ return VK_FORMAT_R16G16B16A16_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
+ return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
+ default:
+ break;
+ }
break;
- }
- break;
- case Maxwell::VertexAttribute::Type::SignedNorm:
- switch (size) {
- case Maxwell::VertexAttribute::Size::Size_8:
- return VK_FORMAT_R8_SNORM;
- case Maxwell::VertexAttribute::Size::Size_8_8:
- return VK_FORMAT_R8G8_SNORM;
- case Maxwell::VertexAttribute::Size::Size_8_8_8:
- return VK_FORMAT_R8G8B8_SNORM;
- case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
- return VK_FORMAT_R8G8B8A8_SNORM;
- case Maxwell::VertexAttribute::Size::Size_16:
- return VK_FORMAT_R16_SNORM;
- case Maxwell::VertexAttribute::Size::Size_16_16:
- return VK_FORMAT_R16G16_SNORM;
- case Maxwell::VertexAttribute::Size::Size_16_16_16:
- return VK_FORMAT_R16G16B16_SNORM;
- case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return VK_FORMAT_R16G16B16A16_SNORM;
- case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
- return VK_FORMAT_A2B10G10R10_SNORM_PACK32;
- default:
+ case Maxwell::VertexAttribute::Type::SignedNorm:
+ switch (size) {
+ case Maxwell::VertexAttribute::Size::Size_8:
+ return VK_FORMAT_R8_SNORM;
+ case Maxwell::VertexAttribute::Size::Size_8_8:
+ return VK_FORMAT_R8G8_SNORM;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8:
+ return VK_FORMAT_R8G8B8_SNORM;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
+ return VK_FORMAT_R8G8B8A8_SNORM;
+ case Maxwell::VertexAttribute::Size::Size_16:
+ return VK_FORMAT_R16_SNORM;
+ case Maxwell::VertexAttribute::Size::Size_16_16:
+ return VK_FORMAT_R16G16_SNORM;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16:
+ return VK_FORMAT_R16G16B16_SNORM;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
+ return VK_FORMAT_R16G16B16A16_SNORM;
+ case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
+ return VK_FORMAT_A2B10G10R10_SNORM_PACK32;
+ default:
+ break;
+ }
break;
- }
- break;
- case Maxwell::VertexAttribute::Type::UnsignedScaled:
- switch (size) {
- case Maxwell::VertexAttribute::Size::Size_8:
- return VK_FORMAT_R8_USCALED;
- case Maxwell::VertexAttribute::Size::Size_8_8:
- return VK_FORMAT_R8G8_USCALED;
- case Maxwell::VertexAttribute::Size::Size_8_8_8:
- return VK_FORMAT_R8G8B8_USCALED;
- case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
- return VK_FORMAT_R8G8B8A8_USCALED;
- case Maxwell::VertexAttribute::Size::Size_16:
- return VK_FORMAT_R16_USCALED;
- case Maxwell::VertexAttribute::Size::Size_16_16:
- return VK_FORMAT_R16G16_USCALED;
- case Maxwell::VertexAttribute::Size::Size_16_16_16:
- return VK_FORMAT_R16G16B16_USCALED;
- case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return VK_FORMAT_R16G16B16A16_USCALED;
- case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
- return VK_FORMAT_A2B10G10R10_USCALED_PACK32;
- default:
+ case Maxwell::VertexAttribute::Type::UnsignedScaled:
+ switch (size) {
+ case Maxwell::VertexAttribute::Size::Size_8:
+ return VK_FORMAT_R8_USCALED;
+ case Maxwell::VertexAttribute::Size::Size_8_8:
+ return VK_FORMAT_R8G8_USCALED;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8:
+ return VK_FORMAT_R8G8B8_USCALED;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
+ return VK_FORMAT_R8G8B8A8_USCALED;
+ case Maxwell::VertexAttribute::Size::Size_16:
+ return VK_FORMAT_R16_USCALED;
+ case Maxwell::VertexAttribute::Size::Size_16_16:
+ return VK_FORMAT_R16G16_USCALED;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16:
+ return VK_FORMAT_R16G16B16_USCALED;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
+ return VK_FORMAT_R16G16B16A16_USCALED;
+ case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
+ return VK_FORMAT_A2B10G10R10_USCALED_PACK32;
+ default:
+ break;
+ }
break;
- }
- break;
- case Maxwell::VertexAttribute::Type::SignedScaled:
- switch (size) {
- case Maxwell::VertexAttribute::Size::Size_8:
- return VK_FORMAT_R8_SSCALED;
- case Maxwell::VertexAttribute::Size::Size_8_8:
- return VK_FORMAT_R8G8_SSCALED;
- case Maxwell::VertexAttribute::Size::Size_8_8_8:
- return VK_FORMAT_R8G8B8_SSCALED;
- case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
- return VK_FORMAT_R8G8B8A8_SSCALED;
- case Maxwell::VertexAttribute::Size::Size_16:
- return VK_FORMAT_R16_SSCALED;
- case Maxwell::VertexAttribute::Size::Size_16_16:
- return VK_FORMAT_R16G16_SSCALED;
- case Maxwell::VertexAttribute::Size::Size_16_16_16:
- return VK_FORMAT_R16G16B16_SSCALED;
- case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return VK_FORMAT_R16G16B16A16_SSCALED;
- case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
- return VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
- default:
+ case Maxwell::VertexAttribute::Type::SignedScaled:
+ switch (size) {
+ case Maxwell::VertexAttribute::Size::Size_8:
+ return VK_FORMAT_R8_SSCALED;
+ case Maxwell::VertexAttribute::Size::Size_8_8:
+ return VK_FORMAT_R8G8_SSCALED;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8:
+ return VK_FORMAT_R8G8B8_SSCALED;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
+ return VK_FORMAT_R8G8B8A8_SSCALED;
+ case Maxwell::VertexAttribute::Size::Size_16:
+ return VK_FORMAT_R16_SSCALED;
+ case Maxwell::VertexAttribute::Size::Size_16_16:
+ return VK_FORMAT_R16G16_SSCALED;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16:
+ return VK_FORMAT_R16G16B16_SSCALED;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
+ return VK_FORMAT_R16G16B16A16_SSCALED;
+ case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
+ return VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
+ default:
+ break;
+ }
break;
- }
- break;
- case Maxwell::VertexAttribute::Type::UnsignedInt:
- switch (size) {
- case Maxwell::VertexAttribute::Size::Size_8:
- return VK_FORMAT_R8_UINT;
- case Maxwell::VertexAttribute::Size::Size_8_8:
- return VK_FORMAT_R8G8_UINT;
- case Maxwell::VertexAttribute::Size::Size_8_8_8:
- return VK_FORMAT_R8G8B8_UINT;
- case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
- return VK_FORMAT_R8G8B8A8_UINT;
- case Maxwell::VertexAttribute::Size::Size_16:
- return VK_FORMAT_R16_UINT;
- case Maxwell::VertexAttribute::Size::Size_16_16:
- return VK_FORMAT_R16G16_UINT;
- case Maxwell::VertexAttribute::Size::Size_16_16_16:
- return VK_FORMAT_R16G16B16_UINT;
- case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return VK_FORMAT_R16G16B16A16_UINT;
- case Maxwell::VertexAttribute::Size::Size_32:
- return VK_FORMAT_R32_UINT;
- case Maxwell::VertexAttribute::Size::Size_32_32:
- return VK_FORMAT_R32G32_UINT;
- case Maxwell::VertexAttribute::Size::Size_32_32_32:
- return VK_FORMAT_R32G32B32_UINT;
- case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
- return VK_FORMAT_R32G32B32A32_UINT;
- case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
- return VK_FORMAT_A2B10G10R10_UINT_PACK32;
- default:
+ case Maxwell::VertexAttribute::Type::UnsignedInt:
+ switch (size) {
+ case Maxwell::VertexAttribute::Size::Size_8:
+ return VK_FORMAT_R8_UINT;
+ case Maxwell::VertexAttribute::Size::Size_8_8:
+ return VK_FORMAT_R8G8_UINT;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8:
+ return VK_FORMAT_R8G8B8_UINT;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
+ return VK_FORMAT_R8G8B8A8_UINT;
+ case Maxwell::VertexAttribute::Size::Size_16:
+ return VK_FORMAT_R16_UINT;
+ case Maxwell::VertexAttribute::Size::Size_16_16:
+ return VK_FORMAT_R16G16_UINT;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16:
+ return VK_FORMAT_R16G16B16_UINT;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
+ return VK_FORMAT_R16G16B16A16_UINT;
+ case Maxwell::VertexAttribute::Size::Size_32:
+ return VK_FORMAT_R32_UINT;
+ case Maxwell::VertexAttribute::Size::Size_32_32:
+ return VK_FORMAT_R32G32_UINT;
+ case Maxwell::VertexAttribute::Size::Size_32_32_32:
+ return VK_FORMAT_R32G32B32_UINT;
+ case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
+ return VK_FORMAT_R32G32B32A32_UINT;
+ case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
+ return VK_FORMAT_A2B10G10R10_UINT_PACK32;
+ default:
+ break;
+ }
break;
- }
- break;
- case Maxwell::VertexAttribute::Type::SignedInt:
- switch (size) {
- case Maxwell::VertexAttribute::Size::Size_8:
- return VK_FORMAT_R8_SINT;
- case Maxwell::VertexAttribute::Size::Size_8_8:
- return VK_FORMAT_R8G8_SINT;
- case Maxwell::VertexAttribute::Size::Size_8_8_8:
- return VK_FORMAT_R8G8B8_SINT;
- case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
- return VK_FORMAT_R8G8B8A8_SINT;
- case Maxwell::VertexAttribute::Size::Size_16:
- return VK_FORMAT_R16_SINT;
- case Maxwell::VertexAttribute::Size::Size_16_16:
- return VK_FORMAT_R16G16_SINT;
- case Maxwell::VertexAttribute::Size::Size_16_16_16:
- return VK_FORMAT_R16G16B16_SINT;
- case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return VK_FORMAT_R16G16B16A16_SINT;
- case Maxwell::VertexAttribute::Size::Size_32:
- return VK_FORMAT_R32_SINT;
- case Maxwell::VertexAttribute::Size::Size_32_32:
- return VK_FORMAT_R32G32_SINT;
- case Maxwell::VertexAttribute::Size::Size_32_32_32:
- return VK_FORMAT_R32G32B32_SINT;
- case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
- return VK_FORMAT_R32G32B32A32_SINT;
- case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
- return VK_FORMAT_A2B10G10R10_SINT_PACK32;
- default:
+ case Maxwell::VertexAttribute::Type::SignedInt:
+ switch (size) {
+ case Maxwell::VertexAttribute::Size::Size_8:
+ return VK_FORMAT_R8_SINT;
+ case Maxwell::VertexAttribute::Size::Size_8_8:
+ return VK_FORMAT_R8G8_SINT;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8:
+ return VK_FORMAT_R8G8B8_SINT;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
+ return VK_FORMAT_R8G8B8A8_SINT;
+ case Maxwell::VertexAttribute::Size::Size_16:
+ return VK_FORMAT_R16_SINT;
+ case Maxwell::VertexAttribute::Size::Size_16_16:
+ return VK_FORMAT_R16G16_SINT;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16:
+ return VK_FORMAT_R16G16B16_SINT;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
+ return VK_FORMAT_R16G16B16A16_SINT;
+ case Maxwell::VertexAttribute::Size::Size_32:
+ return VK_FORMAT_R32_SINT;
+ case Maxwell::VertexAttribute::Size::Size_32_32:
+ return VK_FORMAT_R32G32_SINT;
+ case Maxwell::VertexAttribute::Size::Size_32_32_32:
+ return VK_FORMAT_R32G32B32_SINT;
+ case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
+ return VK_FORMAT_R32G32B32A32_SINT;
+ case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
+ return VK_FORMAT_A2B10G10R10_SINT_PACK32;
+ default:
+ break;
+ }
break;
- }
- break;
- case Maxwell::VertexAttribute::Type::Float:
- switch (size) {
- case Maxwell::VertexAttribute::Size::Size_16:
- return VK_FORMAT_R16_SFLOAT;
- case Maxwell::VertexAttribute::Size::Size_16_16:
- return VK_FORMAT_R16G16_SFLOAT;
- case Maxwell::VertexAttribute::Size::Size_16_16_16:
- return VK_FORMAT_R16G16B16_SFLOAT;
- case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return VK_FORMAT_R16G16B16A16_SFLOAT;
- case Maxwell::VertexAttribute::Size::Size_32:
- return VK_FORMAT_R32_SFLOAT;
- case Maxwell::VertexAttribute::Size::Size_32_32:
- return VK_FORMAT_R32G32_SFLOAT;
- case Maxwell::VertexAttribute::Size::Size_32_32_32:
- return VK_FORMAT_R32G32B32_SFLOAT;
- case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
- return VK_FORMAT_R32G32B32A32_SFLOAT;
- default:
+ case Maxwell::VertexAttribute::Type::Float:
+ switch (size) {
+ case Maxwell::VertexAttribute::Size::Size_16:
+ return VK_FORMAT_R16_SFLOAT;
+ case Maxwell::VertexAttribute::Size::Size_16_16:
+ return VK_FORMAT_R16G16_SFLOAT;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16:
+ return VK_FORMAT_R16G16B16_SFLOAT;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
+ return VK_FORMAT_R16G16B16A16_SFLOAT;
+ case Maxwell::VertexAttribute::Size::Size_32:
+ return VK_FORMAT_R32_SFLOAT;
+ case Maxwell::VertexAttribute::Size::Size_32_32:
+ return VK_FORMAT_R32G32_SFLOAT;
+ case Maxwell::VertexAttribute::Size::Size_32_32_32:
+ return VK_FORMAT_R32G32B32_SFLOAT;
+ case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
+ return VK_FORMAT_R32G32B32A32_SFLOAT;
+ case Maxwell::VertexAttribute::Size::Size_11_11_10:
+ return VK_FORMAT_B10G11R11_UFLOAT_PACK32;
+ default:
+ break;
+ }
break;
}
- break;
+ return VK_FORMAT_UNDEFINED;
+ })()};
+
+ if (format == VK_FORMAT_UNDEFINED) {
+ UNIMPLEMENTED_MSG("Unimplemented vertex format of type={} and size={}", type, size);
}
- UNIMPLEMENTED_MSG("Unimplemented vertex format of type={} and size={}", type, size);
- return {};
+
+ return device.GetSupportedFormat(format, VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT,
+ FormatType::Buffer);
}
VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison) {
@@ -741,7 +756,7 @@ VkViewportCoordinateSwizzleNV ViewportSwizzle(Maxwell::ViewportSwizzle swizzle)
case Maxwell::ViewportSwizzle::NegativeW:
return VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_W_NV;
}
- UNREACHABLE_MSG("Invalid swizzle={}", swizzle);
+ ASSERT_MSG(false, "Invalid swizzle={}", swizzle);
return {};
}
@@ -754,7 +769,7 @@ VkSamplerReductionMode SamplerReduction(Tegra::Texture::SamplerReduction reducti
case Tegra::Texture::SamplerReduction::Max:
return VK_SAMPLER_REDUCTION_MODE_MAX_EXT;
}
- UNREACHABLE_MSG("Invalid sampler mode={}", static_cast<int>(reduction));
+ ASSERT_MSG(false, "Invalid sampler mode={}", static_cast<int>(reduction));
return VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT;
}
@@ -777,7 +792,7 @@ VkSampleCountFlagBits MsaaMode(Tegra::Texture::MsaaMode msaa_mode) {
case Tegra::Texture::MsaaMode::Msaa4x4:
return VK_SAMPLE_COUNT_16_BIT;
default:
- UNREACHABLE_MSG("Invalid msaa_mode={}", static_cast<int>(msaa_mode));
+ ASSERT_MSG(false, "Invalid msaa_mode={}", static_cast<int>(msaa_mode));
return VK_SAMPLE_COUNT_1_BIT;
}
}
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.h b/src/video_core/renderer_vulkan/maxwell_to_vk.h
index 8a9616039..356d46292 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.h
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.h
@@ -1,10 +1,8 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include "common/common_types.h"
#include "shader_recompiler/stage.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/surface.h"
@@ -50,7 +48,8 @@ VkShaderStageFlagBits ShaderStage(Shader::Stage stage);
VkPrimitiveTopology PrimitiveTopology(const Device& device, Maxwell::PrimitiveTopology topology);
-VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size);
+VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
+ Maxwell::VertexAttribute::Size size);
VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison);
diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h
index 11c160570..b24f3424a 100644
--- a/src/video_core/renderer_vulkan/pipeline_helper.h
+++ b/src/video_core/renderer_vulkan/pipeline_helper.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,7 +7,6 @@
#include <boost/container/small_vector.hpp>
-#include "common/assert.h"
#include "common/common_types.h"
#include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/shader_info.h"
@@ -16,7 +14,6 @@
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/texture_cache/texture_cache.h"
#include "video_core/texture_cache/types.h"
-#include "video_core/textures/texture.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
@@ -171,7 +168,7 @@ private:
};
inline void PushImageDescriptors(TextureCache& texture_cache,
- VKUpdateDescriptorQueue& update_descriptor_queue,
+ UpdateDescriptorQueue& update_descriptor_queue,
const Shader::Info& info, RescalingPushConstant& rescaling,
const VkSampler*& samplers,
const VideoCommon::ImageViewInOut*& views) {
diff --git a/src/video_core/renderer_vulkan/pipeline_statistics.cpp b/src/video_core/renderer_vulkan/pipeline_statistics.cpp
index bfec931a6..fcd160a32 100644
--- a/src/video_core/renderer_vulkan/pipeline_statistics.cpp
+++ b/src/video_core/renderer_vulkan/pipeline_statistics.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
@@ -57,7 +56,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 +65,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/pipeline_statistics.h b/src/video_core/renderer_vulkan/pipeline_statistics.h
index b61840107..197bb0936 100644
--- a/src/video_core/renderer_vulkan/pipeline_statistics.h
+++ b/src/video_core/renderer_vulkan/pipeline_statistics.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 74822814d..d8131232a 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -13,16 +12,15 @@
#include <fmt/format.h>
#include "common/logging/log.h"
+#include "common/scope_exit.h"
#include "common/settings.h"
#include "common/telemetry.h"
-#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "core/telemetry_session.h"
#include "video_core/gpu.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h"
-#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_state_tracker.h"
@@ -104,13 +102,13 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
debug_callback(Settings::values.renderer_debug ? CreateDebugCallback(instance) : nullptr),
surface(CreateSurface(instance, render_window)),
device(CreateDevice(instance, dld, *surface)), memory_allocator(device, false),
- state_tracker(gpu), scheduler(device, state_tracker),
+ state_tracker(), scheduler(device, state_tracker),
swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width,
render_window.GetFramebufferLayout().height, false),
blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, scheduler,
screen_info),
- rasterizer(render_window, gpu, gpu.MemoryManager(), cpu_memory, screen_info, device,
- memory_allocator, state_tracker, scheduler) {
+ rasterizer(render_window, gpu, cpu_memory, screen_info, device, memory_allocator,
+ state_tracker, scheduler) {
Report();
} catch (const vk::Exception& exception) {
LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what());
@@ -129,6 +127,11 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
if (!render_window.IsShown()) {
return;
}
+ // Update screen info if the framebuffer size has changed.
+ if (screen_info.width != framebuffer->width || screen_info.height != framebuffer->height) {
+ screen_info.width = framebuffer->width;
+ screen_info.height = framebuffer->height;
+ }
const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset;
const bool use_accelerated =
rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
@@ -139,7 +142,7 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
const auto recreate_swapchain = [&] {
if (!has_been_recreated) {
has_been_recreated = true;
- scheduler.WaitWorker();
+ scheduler.Finish();
}
const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
swapchain.Create(layout.width, layout.height, is_srgb);
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 6dc985109..e7bfecb20 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -1,12 +1,10 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <string>
-#include <vector>
#include "common/dynamic_library.h"
#include "video_core/renderer_base.h"
@@ -67,14 +65,14 @@ private:
vk::DebugUtilsMessenger debug_callback;
vk::SurfaceKHR surface;
- VKScreenInfo screen_info;
+ ScreenInfo screen_info;
Device device;
MemoryAllocator memory_allocator;
StateTracker state_tracker;
- VKScheduler scheduler;
- VKSwapchain swapchain;
- VKBlitScreen blit_screen;
+ Scheduler scheduler;
+ Swapchain swapchain;
+ BlitScreen blit_screen;
RasterizerVulkan rasterizer;
};
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index c71a1f44d..cb7fa2078 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -1,12 +1,10 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cstring>
#include <memory>
-#include <tuple>
#include <vector>
#include "common/assert.h"
@@ -28,7 +26,6 @@
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h"
#include "video_core/renderer_vulkan/vk_fsr.h"
-#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
@@ -96,10 +93,12 @@ 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 Service::android::PixelFormat::Bgra8888:
+ return VK_FORMAT_B8G8R8A8_UNORM;
default:
UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}",
static_cast<u32>(framebuffer.pixel_format));
@@ -109,7 +108,7 @@ VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
} // Anonymous namespace
-struct VKBlitScreen::BufferData {
+struct BlitScreen::BufferData {
struct {
std::array<f32, 4 * 4> modelview_matrix;
} uniform;
@@ -119,10 +118,9 @@ struct VKBlitScreen::BufferData {
// Unaligned image data goes here
};
-VKBlitScreen::VKBlitScreen(Core::Memory::Memory& cpu_memory_,
- Core::Frontend::EmuWindow& render_window_, const Device& device_,
- MemoryAllocator& memory_allocator_, VKSwapchain& swapchain_,
- VKScheduler& scheduler_, const VKScreenInfo& screen_info_)
+BlitScreen::BlitScreen(Core::Memory::Memory& cpu_memory_, Core::Frontend::EmuWindow& render_window_,
+ const Device& device_, MemoryAllocator& memory_allocator_,
+ Swapchain& swapchain_, Scheduler& scheduler_, const ScreenInfo& screen_info_)
: cpu_memory{cpu_memory_}, render_window{render_window_}, device{device_},
memory_allocator{memory_allocator_}, swapchain{swapchain_}, scheduler{scheduler_},
image_count{swapchain.GetImageCount()}, screen_info{screen_info_} {
@@ -132,21 +130,26 @@ VKBlitScreen::VKBlitScreen(Core::Memory::Memory& cpu_memory_,
CreateDynamicResources();
}
-VKBlitScreen::~VKBlitScreen() = default;
+BlitScreen::~BlitScreen() = default;
-void VKBlitScreen::Recreate() {
+void BlitScreen::Recreate() {
CreateDynamicResources();
}
-VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
- const VkFramebuffer& host_framebuffer,
- const Layout::FramebufferLayout layout, VkExtent2D render_area,
- bool use_accelerated) {
+VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
+ const VkFramebuffer& host_framebuffer,
+ const Layout::FramebufferLayout layout, VkExtent2D render_area,
+ bool use_accelerated) {
RefreshResources(framebuffer);
// Finish any pending renderpass
scheduler.RequestOutsideRenderPassOperationContext();
+ if (const auto swapchain_images = swapchain.GetImageCount(); swapchain_images != image_count) {
+ image_count = swapchain_images;
+ Recreate();
+ }
+
const std::size_t image_index = swapchain.GetImageIndex();
scheduler.Wait(resource_ticks[image_index]);
@@ -171,11 +174,12 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
// TODO(Rodrigo): Read this from HLE
constexpr u32 block_height_log2 = 4;
const u32 bytes_per_pixel = GetBytesPerPixel(framebuffer);
- const u64 size_bytes{Tegra::Texture::CalculateSize(true, bytes_per_pixel,
+ const u64 linear_size{GetSizeInBytes(framebuffer)};
+ const u64 tiled_size{Tegra::Texture::CalculateSize(true, bytes_per_pixel,
framebuffer.stride, framebuffer.height,
1, block_height_log2, 0)};
Tegra::Texture::UnswizzleTexture(
- mapped_span.subspan(image_offset, size_bytes), std::span(host_ptr, size_bytes),
+ mapped_span.subspan(image_offset, linear_size), std::span(host_ptr, tiled_size),
bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0);
const VkBufferImageCopy copy{
@@ -420,20 +424,20 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
return *semaphores[image_index];
}
-VkSemaphore VKBlitScreen::DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer,
- bool use_accelerated) {
+VkSemaphore BlitScreen::DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer,
+ bool use_accelerated) {
const std::size_t image_index = swapchain.GetImageIndex();
const VkExtent2D render_area = swapchain.GetSize();
const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
return Draw(framebuffer, *framebuffers[image_index], layout, render_area, use_accelerated);
}
-vk::Framebuffer VKBlitScreen::CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent) {
+vk::Framebuffer BlitScreen::CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent) {
return CreateFramebuffer(image_view, extent, renderpass);
}
-vk::Framebuffer VKBlitScreen::CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent,
- vk::RenderPass& rd) {
+vk::Framebuffer BlitScreen::CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent,
+ vk::RenderPass& rd) {
return device.GetLogical().CreateFramebuffer(VkFramebufferCreateInfo{
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.pNext = nullptr,
@@ -447,17 +451,17 @@ vk::Framebuffer VKBlitScreen::CreateFramebuffer(const VkImageView& image_view, V
});
}
-void VKBlitScreen::CreateStaticResources() {
+void BlitScreen::CreateStaticResources() {
CreateShaders();
+ CreateSampler();
+}
+
+void BlitScreen::CreateDynamicResources() {
CreateSemaphores();
CreateDescriptorPool();
CreateDescriptorSetLayout();
CreateDescriptorSets();
CreatePipelineLayout();
- CreateSampler();
-}
-
-void VKBlitScreen::CreateDynamicResources() {
CreateRenderPass();
CreateFramebuffers();
CreateGraphicsPipeline();
@@ -467,7 +471,7 @@ void VKBlitScreen::CreateDynamicResources() {
}
}
-void VKBlitScreen::RefreshResources(const Tegra::FramebufferConfig& framebuffer) {
+void BlitScreen::RefreshResources(const Tegra::FramebufferConfig& framebuffer) {
if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) {
if (!fsr) {
CreateFSR();
@@ -487,7 +491,7 @@ void VKBlitScreen::RefreshResources(const Tegra::FramebufferConfig& framebuffer)
CreateRawImages(framebuffer);
}
-void VKBlitScreen::CreateShaders() {
+void BlitScreen::CreateShaders() {
vertex_shader = BuildShader(device, VULKAN_PRESENT_VERT_SPV);
fxaa_vertex_shader = BuildShader(device, FXAA_VERT_SPV);
fxaa_fragment_shader = BuildShader(device, FXAA_FRAG_SPV);
@@ -501,12 +505,12 @@ void VKBlitScreen::CreateShaders() {
}
}
-void VKBlitScreen::CreateSemaphores() {
+void BlitScreen::CreateSemaphores() {
semaphores.resize(image_count);
std::ranges::generate(semaphores, [this] { return device.GetLogical().CreateSemaphore(); });
}
-void VKBlitScreen::CreateDescriptorPool() {
+void BlitScreen::CreateDescriptorPool() {
const std::array<VkDescriptorPoolSize, 2> pool_sizes{{
{
.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
@@ -546,11 +550,11 @@ void VKBlitScreen::CreateDescriptorPool() {
aa_descriptor_pool = device.GetLogical().CreateDescriptorPool(ci_aa);
}
-void VKBlitScreen::CreateRenderPass() {
+void BlitScreen::CreateRenderPass() {
renderpass = CreateRenderPassImpl(swapchain.GetImageViewFormat());
}
-vk::RenderPass VKBlitScreen::CreateRenderPassImpl(VkFormat format, bool is_present) {
+vk::RenderPass BlitScreen::CreateRenderPassImpl(VkFormat format, bool is_present) {
const VkAttachmentDescription color_attachment{
.flags = 0,
.format = format,
@@ -606,7 +610,7 @@ vk::RenderPass VKBlitScreen::CreateRenderPassImpl(VkFormat format, bool is_prese
return device.GetLogical().CreateRenderPass(renderpass_ci);
}
-void VKBlitScreen::CreateDescriptorSetLayout() {
+void BlitScreen::CreateDescriptorSetLayout() {
const std::array<VkDescriptorSetLayoutBinding, 2> layout_bindings{{
{
.binding = 0,
@@ -661,7 +665,7 @@ void VKBlitScreen::CreateDescriptorSetLayout() {
aa_descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout(ci_aa);
}
-void VKBlitScreen::CreateDescriptorSets() {
+void BlitScreen::CreateDescriptorSets() {
const std::vector layouts(image_count, *descriptor_set_layout);
const std::vector layouts_aa(image_count, *aa_descriptor_set_layout);
@@ -685,7 +689,7 @@ void VKBlitScreen::CreateDescriptorSets() {
aa_descriptor_sets = aa_descriptor_pool.Allocate(ai_aa);
}
-void VKBlitScreen::CreatePipelineLayout() {
+void BlitScreen::CreatePipelineLayout() {
const VkPipelineLayoutCreateInfo ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.pNext = nullptr,
@@ -708,7 +712,7 @@ void VKBlitScreen::CreatePipelineLayout() {
aa_pipeline_layout = device.GetLogical().CreatePipelineLayout(ci_aa);
}
-void VKBlitScreen::CreateGraphicsPipeline() {
+void BlitScreen::CreateGraphicsPipeline() {
const std::array<VkPipelineShaderStageCreateInfo, 2> bilinear_shader_stages{{
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
@@ -981,7 +985,7 @@ void VKBlitScreen::CreateGraphicsPipeline() {
scaleforce_pipeline = device.GetLogical().CreateGraphicsPipeline(scaleforce_pipeline_ci);
}
-void VKBlitScreen::CreateSampler() {
+void BlitScreen::CreateSampler() {
const VkSamplerCreateInfo ci{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = nullptr,
@@ -1028,7 +1032,7 @@ void VKBlitScreen::CreateSampler() {
nn_sampler = device.GetLogical().CreateSampler(ci_nn);
}
-void VKBlitScreen::CreateFramebuffers() {
+void BlitScreen::CreateFramebuffers() {
const VkExtent2D size{swapchain.GetSize()};
framebuffers.resize(image_count);
@@ -1038,7 +1042,7 @@ void VKBlitScreen::CreateFramebuffers() {
}
}
-void VKBlitScreen::ReleaseRawImages() {
+void BlitScreen::ReleaseRawImages() {
for (const u64 tick : resource_ticks) {
scheduler.Wait(tick);
}
@@ -1053,7 +1057,7 @@ void VKBlitScreen::ReleaseRawImages() {
buffer_commit = MemoryCommit{};
}
-void VKBlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) {
+void BlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) {
const VkBufferCreateInfo ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
@@ -1070,7 +1074,7 @@ void VKBlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuff
buffer_commit = memory_allocator.Commit(buffer, MemoryUsage::Upload);
}
-void VKBlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
+void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
raw_images.resize(image_count);
raw_image_views.resize(image_count);
raw_buffer_commits.resize(image_count);
@@ -1295,8 +1299,8 @@ void VKBlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer)
aa_pipeline = device.GetLogical().CreateGraphicsPipeline(fxaa_pipeline_ci);
}
-void VKBlitScreen::UpdateAADescriptorSet(std::size_t image_index, VkImageView image_view,
- bool nn) const {
+void BlitScreen::UpdateAADescriptorSet(std::size_t image_index, VkImageView image_view,
+ bool nn) const {
const VkDescriptorImageInfo image_info{
.sampler = nn ? *nn_sampler : *sampler,
.imageView = image_view,
@@ -1332,8 +1336,8 @@ void VKBlitScreen::UpdateAADescriptorSet(std::size_t image_index, VkImageView im
device.GetLogical().UpdateDescriptorSets(std::array{sampler_write, sampler_write_2}, {});
}
-void VKBlitScreen::UpdateDescriptorSet(std::size_t image_index, VkImageView image_view,
- bool nn) const {
+void BlitScreen::UpdateDescriptorSet(std::size_t image_index, VkImageView image_view,
+ bool nn) const {
const VkDescriptorBufferInfo buffer_info{
.buffer = *buffer,
.offset = offsetof(BufferData, uniform),
@@ -1375,13 +1379,13 @@ void VKBlitScreen::UpdateDescriptorSet(std::size_t image_index, VkImageView imag
device.GetLogical().UpdateDescriptorSets(std::array{ubo_write, sampler_write}, {});
}
-void VKBlitScreen::SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const {
+void BlitScreen::SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const {
data.uniform.modelview_matrix =
MakeOrthographicMatrix(static_cast<f32>(layout.width), static_cast<f32>(layout.height));
}
-void VKBlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer,
- const Layout::FramebufferLayout layout) const {
+void BlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer,
+ const Layout::FramebufferLayout layout) const {
const auto& framebuffer_transform_flags = framebuffer.transform_flags;
const auto& framebuffer_crop_rect = framebuffer.crop_rect;
@@ -1390,9 +1394,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;
@@ -1403,11 +1407,15 @@ void VKBlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfi
break;
}
- 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 left_start{};
+ if (framebuffer_crop_rect.Top() > 0) {
+ left_start = static_cast<f32>(framebuffer_crop_rect.Top()) /
+ static_cast<f32>(framebuffer_crop_rect.Bottom());
+ }
+ 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) {
@@ -1426,13 +1434,16 @@ void VKBlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfi
const auto y = static_cast<f32>(screen.top);
const auto w = static_cast<f32>(screen.GetWidth());
const auto h = static_cast<f32>(screen.GetHeight());
- data.vertices[0] = ScreenRectVertex(x, y, texcoords.top * scale_u, left * scale_v);
- data.vertices[1] = ScreenRectVertex(x + w, y, texcoords.bottom * scale_u, left * scale_v);
- data.vertices[2] = ScreenRectVertex(x, y + h, texcoords.top * scale_u, right * scale_v);
- data.vertices[3] = ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v);
+ data.vertices[0] = ScreenRectVertex(x, y, texcoords.top * scale_u, left_start + left * scale_v);
+ data.vertices[1] =
+ ScreenRectVertex(x + w, y, texcoords.bottom * scale_u, left_start + left * scale_v);
+ data.vertices[2] =
+ ScreenRectVertex(x, y + h, texcoords.top * scale_u, left_start + right * scale_v);
+ data.vertices[3] =
+ ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, left_start + right * scale_v);
}
-void VKBlitScreen::CreateFSR() {
+void BlitScreen::CreateFSR() {
const auto& layout = render_window.GetFramebufferLayout();
const VkExtent2D fsr_size{
.width = layout.screen.GetWidth(),
@@ -1441,12 +1452,12 @@ void VKBlitScreen::CreateFSR() {
fsr = std::make_unique<FSR>(device, memory_allocator, image_count, fsr_size);
}
-u64 VKBlitScreen::CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const {
+u64 BlitScreen::CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const {
return sizeof(BufferData) + GetSizeInBytes(framebuffer) * image_count;
}
-u64 VKBlitScreen::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
- std::size_t image_index) const {
+u64 BlitScreen::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
+ std::size_t image_index) const {
constexpr auto first_image_offset = static_cast<u64>(sizeof(BufferData));
return first_image_offset + GetSizeInBytes(framebuffer) * image_index;
}
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index bbca71af3..29e2ea925 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -36,23 +35,22 @@ struct ScreenInfo;
class Device;
class FSR;
class RasterizerVulkan;
-class VKScheduler;
-class VKSwapchain;
+class Scheduler;
+class Swapchain;
-struct VKScreenInfo {
+struct ScreenInfo {
VkImageView image_view{};
u32 width{};
u32 height{};
bool is_srgb{};
};
-class VKBlitScreen {
+class BlitScreen {
public:
- explicit VKBlitScreen(Core::Memory::Memory& cpu_memory,
- Core::Frontend::EmuWindow& render_window, const Device& device,
- MemoryAllocator& memory_manager, VKSwapchain& swapchain,
- VKScheduler& scheduler, const VKScreenInfo& screen_info);
- ~VKBlitScreen();
+ explicit BlitScreen(Core::Memory::Memory& cpu_memory, Core::Frontend::EmuWindow& render_window,
+ const Device& device, MemoryAllocator& memory_manager, Swapchain& swapchain,
+ Scheduler& scheduler, const ScreenInfo& screen_info);
+ ~BlitScreen();
void Recreate();
@@ -109,10 +107,10 @@ private:
Core::Frontend::EmuWindow& render_window;
const Device& device;
MemoryAllocator& memory_allocator;
- VKSwapchain& swapchain;
- VKScheduler& scheduler;
- const std::size_t image_count;
- const VKScreenInfo& screen_info;
+ Swapchain& swapchain;
+ Scheduler& scheduler;
+ std::size_t image_count;
+ const ScreenInfo& screen_info;
vk::ShaderModule vertex_shader;
vk::ShaderModule fxaa_vertex_shader;
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 5ffd93499..558b8db56 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -47,7 +46,7 @@ size_t BytesPerIndex(VkIndexType index_type) {
case VK_INDEX_TYPE_UINT32:
return 4;
default:
- UNREACHABLE_MSG("Invalid index type={}", index_type);
+ ASSERT_MSG(false, "Invalid index type={}", index_type);
return 1;
}
}
@@ -125,8 +124,8 @@ VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat
}
BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& memory_allocator_,
- VKScheduler& scheduler_, StagingBufferPool& staging_pool_,
- VKUpdateDescriptorQueue& update_descriptor_queue_,
+ Scheduler& scheduler_, StagingBufferPool& staging_pool_,
+ UpdateDescriptorQueue& update_descriptor_queue_,
DescriptorPool& descriptor_pool)
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
staging_pool{staging_pool_}, update_descriptor_queue{update_descriptor_queue_},
@@ -141,6 +140,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();
}
@@ -355,7 +366,7 @@ void BufferCacheRuntime::ReserveQuadArrayLUT(u32 num_indices, bool wait_for_idle
std::memcpy(staging_data, MakeQuadIndices<u32>(quad, first).data(), quad_size);
break;
default:
- UNREACHABLE();
+ ASSERT(false);
break;
}
staging_data += quad_size;
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 1ee0d8420..a15c8b39b 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -17,7 +16,7 @@ namespace Vulkan {
class Device;
class DescriptorPool;
-class VKScheduler;
+class Scheduler;
class BufferCacheRuntime;
@@ -59,12 +58,18 @@ class BufferCacheRuntime {
public:
explicit BufferCacheRuntime(const Device& device_, MemoryAllocator& memory_manager_,
- VKScheduler& scheduler_, StagingBufferPool& staging_pool_,
- VKUpdateDescriptorQueue& update_descriptor_queue_,
+ Scheduler& scheduler_, StagingBufferPool& staging_pool_,
+ UpdateDescriptorQueue& update_descriptor_queue_,
DescriptorPool& descriptor_pool);
void Finish();
+ u64 GetDeviceLocalMemory() const;
+
+ u64 GetDeviceMemoryUsage() const;
+
+ bool CanReportMemoryUsage() const;
+
[[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size);
[[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size);
@@ -119,9 +124,9 @@ private:
const Device& device;
MemoryAllocator& memory_allocator;
- VKScheduler& scheduler;
+ Scheduler& scheduler;
StagingBufferPool& staging_pool;
- VKUpdateDescriptorQueue& update_descriptor_queue;
+ UpdateDescriptorQueue& update_descriptor_queue;
vk::Buffer quad_array_lut;
MemoryCommit quad_array_lut_commit;
diff --git a/src/video_core/renderer_vulkan/vk_command_pool.cpp b/src/video_core/renderer_vulkan/vk_command_pool.cpp
index d8e92ac0e..2f09de1c1 100644
--- a/src/video_core/renderer_vulkan/vk_command_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_command_pool.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstddef>
diff --git a/src/video_core/renderer_vulkan/vk_command_pool.h b/src/video_core/renderer_vulkan/vk_command_pool.h
index 61c26a22a..ec1647f01 100644
--- a/src/video_core/renderer_vulkan/vk_command_pool.h
+++ b/src/video_core/renderer_vulkan/vk_command_pool.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
index 3e96c0f60..241d7573e 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -1,13 +1,11 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include <cstring>
+#include <array>
#include <memory>
#include <optional>
#include <utility>
-#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"
#include "common/div_ceil.h"
@@ -22,15 +20,12 @@
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/texture_cache/accelerated_swizzle.h"
#include "video_core/texture_cache/types.h"
-#include "video_core/textures/astc.h"
#include "video_core/textures/decoders.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-using Tegra::Texture::SWIZZLE_TABLE;
-
namespace {
constexpr u32 ASTC_BINDING_INPUT_BUFFER = 0;
@@ -203,9 +198,9 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
ComputePass::~ComputePass() = default;
-Uint8Pass::Uint8Pass(const Device& device_, VKScheduler& scheduler_,
- DescriptorPool& descriptor_pool, StagingBufferPool& staging_buffer_pool_,
- VKUpdateDescriptorQueue& update_descriptor_queue_)
+Uint8Pass::Uint8Pass(const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool,
+ StagingBufferPool& staging_buffer_pool_,
+ UpdateDescriptorQueue& update_descriptor_queue_)
: ComputePass(device_, descriptor_pool, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS,
INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO, {},
VULKAN_UINT8_COMP_SPV),
@@ -244,10 +239,10 @@ std::pair<VkBuffer, VkDeviceSize> Uint8Pass::Assemble(u32 num_vertices, VkBuffer
return {staging.buffer, staging.offset};
}
-QuadIndexedPass::QuadIndexedPass(const Device& device_, VKScheduler& scheduler_,
+QuadIndexedPass::QuadIndexedPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
- VKUpdateDescriptorQueue& update_descriptor_queue_)
+ UpdateDescriptorQueue& update_descriptor_queue_)
: ComputePass(device_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS,
INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(u32) * 2>, VULKAN_QUAD_INDEXED_COMP_SPV),
@@ -268,7 +263,7 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
case Tegra::Engines::Maxwell3D::Regs::IndexFormat::UnsignedInt:
return 2;
}
- UNREACHABLE();
+ ASSERT(false);
return 2;
}();
const u32 input_size = num_vertices << index_shift;
@@ -292,7 +287,7 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_INDEX_READ_BIT,
};
- const std::array push_constants{base_vertex, index_shift};
+ const std::array<u32, 2> push_constants{base_vertex, index_shift};
const VkDescriptorSet set = descriptor_allocator.Commit();
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
@@ -306,10 +301,10 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
return {staging.buffer, staging.offset};
}
-ASTCDecoderPass::ASTCDecoderPass(const Device& device_, VKScheduler& scheduler_,
+ASTCDecoderPass::ASTCDecoderPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
- VKUpdateDescriptorQueue& update_descriptor_queue_,
+ UpdateDescriptorQueue& update_descriptor_queue_,
MemoryAllocator& memory_allocator_)
: ComputePass(device_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS,
ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO,
@@ -331,31 +326,32 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
const VkImageAspectFlags aspect_mask = image.AspectMask();
const VkImage vk_image = image.Handle();
const bool is_initialized = image.ExchangeInitialization();
- scheduler.Record(
- [vk_pipeline, vk_image, aspect_mask, is_initialized](vk::CommandBuffer cmdbuf) {
- const VkImageMemoryBarrier image_barrier{
- .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
- .pNext = nullptr,
- .srcAccessMask = is_initialized ? VK_ACCESS_SHADER_WRITE_BIT : VkAccessFlags{},
- .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
- .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED,
- .newLayout = VK_IMAGE_LAYOUT_GENERAL,
- .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
- .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
- .image = vk_image,
- .subresourceRange{
- .aspectMask = aspect_mask,
- .baseMipLevel = 0,
- .levelCount = VK_REMAINING_MIP_LEVELS,
- .baseArrayLayer = 0,
- .layerCount = VK_REMAINING_ARRAY_LAYERS,
- },
- };
- cmdbuf.PipelineBarrier(is_initialized ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT
- : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
- VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier);
- cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline);
- });
+ scheduler.Record([vk_pipeline, vk_image, aspect_mask,
+ is_initialized](vk::CommandBuffer cmdbuf) {
+ const VkImageMemoryBarrier image_barrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = static_cast<VkAccessFlags>(is_initialized ? VK_ACCESS_SHADER_WRITE_BIT
+ : VK_ACCESS_NONE),
+ .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
+ .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = vk_image,
+ .subresourceRange{
+ .aspectMask = aspect_mask,
+ .baseMipLevel = 0,
+ .levelCount = VK_REMAINING_MIP_LEVELS,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ };
+ cmdbuf.PipelineBarrier(is_initialized ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT
+ : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier);
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline);
+ });
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
const size_t input_offset = swizzle.buffer_offset + map.offset;
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 8U);
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h
index c7b92cce0..dcc691a8e 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -21,8 +20,8 @@ namespace Vulkan {
class Device;
class StagingBufferPool;
-class VKScheduler;
-class VKUpdateDescriptorQueue;
+class Scheduler;
+class UpdateDescriptorQueue;
class Image;
struct StagingBufferRef;
@@ -49,9 +48,9 @@ private:
class Uint8Pass final : public ComputePass {
public:
- explicit Uint8Pass(const Device& device_, VKScheduler& scheduler_,
+ explicit Uint8Pass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_, StagingBufferPool& staging_buffer_pool_,
- VKUpdateDescriptorQueue& update_descriptor_queue_);
+ UpdateDescriptorQueue& update_descriptor_queue_);
~Uint8Pass();
/// Assemble uint8 indices into an uint16 index buffer
@@ -60,17 +59,17 @@ public:
u32 src_offset);
private:
- VKScheduler& scheduler;
+ Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
- VKUpdateDescriptorQueue& update_descriptor_queue;
+ UpdateDescriptorQueue& update_descriptor_queue;
};
class QuadIndexedPass final : public ComputePass {
public:
- explicit QuadIndexedPass(const Device& device_, VKScheduler& scheduler_,
+ explicit QuadIndexedPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
- VKUpdateDescriptorQueue& update_descriptor_queue_);
+ UpdateDescriptorQueue& update_descriptor_queue_);
~QuadIndexedPass();
std::pair<VkBuffer, VkDeviceSize> Assemble(
@@ -78,17 +77,17 @@ public:
u32 base_vertex, VkBuffer src_buffer, u32 src_offset);
private:
- VKScheduler& scheduler;
+ Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
- VKUpdateDescriptorQueue& update_descriptor_queue;
+ UpdateDescriptorQueue& update_descriptor_queue;
};
class ASTCDecoderPass final : public ComputePass {
public:
- explicit ASTCDecoderPass(const Device& device_, VKScheduler& scheduler_,
+ explicit ASTCDecoderPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
- VKUpdateDescriptorQueue& update_descriptor_queue_,
+ UpdateDescriptorQueue& update_descriptor_queue_,
MemoryAllocator& memory_allocator_);
~ASTCDecoderPass();
@@ -96,9 +95,9 @@ public:
std::span<const VideoCommon::SwizzleParameters> swizzles);
private:
- VKScheduler& scheduler;
+ Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
- VKUpdateDescriptorQueue& update_descriptor_queue;
+ UpdateDescriptorQueue& update_descriptor_queue;
MemoryAllocator& memory_allocator;
};
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
index de36bcdb7..7906e11a8 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <vector>
@@ -26,7 +25,7 @@ using Shader::Backend::SPIRV::RESCALING_LAYOUT_WORDS_OFFSET;
using Tegra::Texture::TexturePair;
ComputePipeline::ComputePipeline(const Device& device_, DescriptorPool& descriptor_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue_,
+ UpdateDescriptorQueue& update_descriptor_queue_,
Common::ThreadWorker* thread_worker,
PipelineStatistics* pipeline_statistics,
VideoCore::ShaderNotify* shader_notify, const Shader::Info& info_,
@@ -77,7 +76,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) {
@@ -92,7 +91,7 @@ ComputePipeline::ComputePipeline(const Device& device_, DescriptorPool& descript
}
void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
- Tegra::MemoryManager& gpu_memory, VKScheduler& scheduler,
+ Tegra::MemoryManager& gpu_memory, Scheduler& scheduler,
BufferCache& buffer_cache, TextureCache& texture_cache) {
update_descriptor_queue.Acquire();
@@ -127,8 +126,8 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
const u32 secondary_offset{desc.secondary_cbuf_offset + index_offset};
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].Address() +
secondary_offset};
- const u32 lhs_raw{gpu_memory.Read<u32>(addr)};
- const u32 rhs_raw{gpu_memory.Read<u32>(separate_addr)};
+ const u32 lhs_raw{gpu_memory.Read<u32>(addr) << desc.shift_left};
+ const u32 rhs_raw{gpu_memory.Read<u32>(separate_addr) << desc.secondary_shift_left};
return TexturePair(lhs_raw | rhs_raw, via_header_index);
}
}
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h
index 8c4b0a301..9879735fe 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -11,7 +10,6 @@
#include "common/common_types.h"
#include "common/thread_worker.h"
#include "shader_recompiler/shader_info.h"
-#include "video_core/memory_manager.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
@@ -26,12 +24,12 @@ namespace Vulkan {
class Device;
class PipelineStatistics;
-class VKScheduler;
+class Scheduler;
class ComputePipeline {
public:
explicit ComputePipeline(const Device& device, DescriptorPool& descriptor_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue,
+ UpdateDescriptorQueue& update_descriptor_queue,
Common::ThreadWorker* thread_worker,
PipelineStatistics* pipeline_statistics,
VideoCore::ShaderNotify* shader_notify, const Shader::Info& info,
@@ -44,11 +42,11 @@ public:
ComputePipeline(const ComputePipeline&) = delete;
void Configure(Tegra::Engines::KeplerCompute& kepler_compute, Tegra::MemoryManager& gpu_memory,
- VKScheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache);
+ Scheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache);
private:
const Device& device;
- VKUpdateDescriptorQueue& update_descriptor_queue;
+ UpdateDescriptorQueue& update_descriptor_queue;
Shader::Info info;
VideoCommon::ComputeUniformBufferSizes uniform_buffer_sizes{};
diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
index d87da2a34..c7196b64e 100644
--- a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <mutex>
@@ -122,7 +121,7 @@ vk::DescriptorSets DescriptorAllocator::AllocateDescriptors(size_t count) {
throw vk::Exception(VK_ERROR_OUT_OF_POOL_MEMORY);
}
-DescriptorPool::DescriptorPool(const Device& device_, VKScheduler& scheduler)
+DescriptorPool::DescriptorPool(const Device& device_, Scheduler& scheduler)
: device{device_}, master_semaphore{scheduler.GetMasterSemaphore()} {}
DescriptorPool::~DescriptorPool() = default;
diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.h b/src/video_core/renderer_vulkan/vk_descriptor_pool.h
index 59466aac5..bd6696b07 100644
--- a/src/video_core/renderer_vulkan/vk_descriptor_pool.h
+++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -15,7 +14,7 @@
namespace Vulkan {
class Device;
-class VKScheduler;
+class Scheduler;
struct DescriptorBank;
@@ -63,7 +62,7 @@ private:
class DescriptorPool {
public:
- explicit DescriptorPool(const Device& device, VKScheduler& scheduler);
+ explicit DescriptorPool(const Device& device, Scheduler& scheduler);
~DescriptorPool();
DescriptorPool& operator=(const DescriptorPool&) = delete;
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.cpp b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
index 3bec48d14..0214b103a 100644
--- a/src/video_core/renderer_vulkan/vk_fence_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
@@ -9,15 +8,11 @@
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/vulkan_common/vulkan_device.h"
-#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-InnerFence::InnerFence(VKScheduler& scheduler_, u32 payload_, bool is_stubbed_)
- : FenceBase{payload_, is_stubbed_}, scheduler{scheduler_} {}
-
-InnerFence::InnerFence(VKScheduler& scheduler_, GPUVAddr address_, u32 payload_, bool is_stubbed_)
- : FenceBase{address_, payload_, is_stubbed_}, scheduler{scheduler_} {}
+InnerFence::InnerFence(Scheduler& scheduler_, bool is_stubbed_)
+ : FenceBase{is_stubbed_}, scheduler{scheduler_} {}
InnerFence::~InnerFence() = default;
@@ -44,30 +39,25 @@ void InnerFence::Wait() {
scheduler.Wait(wait_tick);
}
-VKFenceManager::VKFenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
- TextureCache& texture_cache_, BufferCache& buffer_cache_,
- VKQueryCache& query_cache_, const Device& device_,
- VKScheduler& scheduler_)
+FenceManager::FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
+ TextureCache& texture_cache_, BufferCache& buffer_cache_,
+ QueryCache& query_cache_, const Device& device_, Scheduler& scheduler_)
: GenericFenceManager{rasterizer_, gpu_, texture_cache_, buffer_cache_, query_cache_},
scheduler{scheduler_} {}
-Fence VKFenceManager::CreateFence(u32 value, bool is_stubbed) {
- return std::make_shared<InnerFence>(scheduler, value, is_stubbed);
-}
-
-Fence VKFenceManager::CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) {
- return std::make_shared<InnerFence>(scheduler, addr, value, is_stubbed);
+Fence FenceManager::CreateFence(bool is_stubbed) {
+ return std::make_shared<InnerFence>(scheduler, is_stubbed);
}
-void VKFenceManager::QueueFence(Fence& fence) {
+void FenceManager::QueueFence(Fence& fence) {
fence->Queue();
}
-bool VKFenceManager::IsFenceSignaled(Fence& fence) const {
+bool FenceManager::IsFenceSignaled(Fence& fence) const {
return fence->IsSignaled();
}
-void VKFenceManager::WaitFence(Fence& fence) {
+void FenceManager::WaitFence(Fence& fence) {
fence->Wait();
}
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.h b/src/video_core/renderer_vulkan/vk_fence_manager.h
index 2f8322d29..7fe2afcd9 100644
--- a/src/video_core/renderer_vulkan/vk_fence_manager.h
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -9,7 +8,6 @@
#include "video_core/fence_manager.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
-#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Core {
class System;
@@ -22,13 +20,12 @@ class RasterizerInterface;
namespace Vulkan {
class Device;
-class VKQueryCache;
-class VKScheduler;
+class QueryCache;
+class Scheduler;
class InnerFence : public VideoCommon::FenceBase {
public:
- explicit InnerFence(VKScheduler& scheduler_, u32 payload_, bool is_stubbed_);
- explicit InnerFence(VKScheduler& scheduler_, GPUVAddr address_, u32 payload_, bool is_stubbed_);
+ explicit InnerFence(Scheduler& scheduler_, bool is_stubbed_);
~InnerFence();
void Queue();
@@ -38,30 +35,27 @@ public:
void Wait();
private:
- VKScheduler& scheduler;
+ Scheduler& scheduler;
u64 wait_tick = 0;
};
using Fence = std::shared_ptr<InnerFence>;
-using GenericFenceManager =
- VideoCommon::FenceManager<Fence, TextureCache, BufferCache, VKQueryCache>;
+using GenericFenceManager = VideoCommon::FenceManager<Fence, TextureCache, BufferCache, QueryCache>;
-class VKFenceManager final : public GenericFenceManager {
+class FenceManager final : public GenericFenceManager {
public:
- explicit VKFenceManager(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
- TextureCache& texture_cache, BufferCache& buffer_cache,
- VKQueryCache& query_cache, const Device& device,
- VKScheduler& scheduler);
+ explicit FenceManager(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
+ TextureCache& texture_cache, BufferCache& buffer_cache,
+ QueryCache& query_cache, const Device& device, Scheduler& scheduler);
protected:
- Fence CreateFence(u32 value, bool is_stubbed) override;
- Fence CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) override;
+ Fence CreateFence(bool is_stubbed) override;
void QueueFence(Fence& fence) override;
bool IsFenceSignaled(Fence& fence) const override;
void WaitFence(Fence& fence) override;
private:
- VKScheduler& scheduler;
+ Scheduler& scheduler;
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_fsr.cpp b/src/video_core/renderer_vulkan/vk_fsr.cpp
index b630090e8..dd450169e 100644
--- a/src/video_core/renderer_vulkan/vk_fsr.cpp
+++ b/src/video_core/renderer_vulkan/vk_fsr.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cmath>
#include "common/bit_cast.h"
@@ -173,7 +172,7 @@ FSR::FSR(const Device& device_, MemoryAllocator& memory_allocator_, size_t image
CreatePipeline();
}
-VkImageView FSR::Draw(VKScheduler& scheduler, size_t image_index, VkImageView image_view,
+VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImageView image_view,
VkExtent2D input_image_extent, const Common::Rectangle<int>& crop_rect) {
UpdateDescriptorSet(image_index, image_view);
diff --git a/src/video_core/renderer_vulkan/vk_fsr.h b/src/video_core/renderer_vulkan/vk_fsr.h
index 6bbec3d36..5d872861f 100644
--- a/src/video_core/renderer_vulkan/vk_fsr.h
+++ b/src/video_core/renderer_vulkan/vk_fsr.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -11,13 +10,13 @@
namespace Vulkan {
class Device;
-class VKScheduler;
+class Scheduler;
class FSR {
public:
explicit FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count,
VkExtent2D output_size);
- VkImageView Draw(VKScheduler& scheduler, size_t image_index, VkImageView image_view,
+ VkImageView Draw(Scheduler& scheduler, size_t image_index, VkImageView image_view,
VkExtent2D input_image_extent, const Common::Rectangle<int>& crop_rect);
private:
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index d514b71d0..f47786f48 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <span>
@@ -216,15 +215,14 @@ ConfigureFuncPtr ConfigureFunc(const std::array<vk::ShaderModule, NUM_STAGES>& m
} // Anonymous namespace
GraphicsPipeline::GraphicsPipeline(
- Tegra::Engines::Maxwell3D& maxwell3d_, Tegra::MemoryManager& gpu_memory_,
- VKScheduler& scheduler_, BufferCache& buffer_cache_, TextureCache& texture_cache_,
+ Scheduler& scheduler_, BufferCache& buffer_cache_, TextureCache& texture_cache_,
VideoCore::ShaderNotify* shader_notify, const Device& device_, DescriptorPool& descriptor_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue_, Common::ThreadWorker* worker_thread,
+ UpdateDescriptorQueue& update_descriptor_queue_, Common::ThreadWorker* worker_thread,
PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache,
const GraphicsPipelineCacheKey& key_, std::array<vk::ShaderModule, NUM_STAGES> stages,
const std::array<const Shader::Info*, NUM_STAGES>& infos)
- : key{key_}, maxwell3d{maxwell3d_}, gpu_memory{gpu_memory_}, device{device_},
- texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, scheduler{scheduler_},
+ : key{key_}, device{device_}, texture_cache{texture_cache_},
+ buffer_cache{buffer_cache_}, scheduler{scheduler_},
update_descriptor_queue{update_descriptor_queue_}, spv_modules{std::move(stages)} {
if (shader_notify) {
shader_notify->MarkShaderBuilding();
@@ -258,7 +256,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) {
@@ -289,7 +287,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
buffer_cache.SetUniformBuffersState(enabled_uniform_buffer_masks, &uniform_buffer_sizes);
- const auto& regs{maxwell3d.regs};
+ const auto& regs{maxwell3d->regs};
const bool via_header_index{regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex};
const auto config_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
const Shader::Info& info{stage_infos[stage]};
@@ -303,7 +301,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
++ssbo_index;
}
}
- const auto& cbufs{maxwell3d.state.shader_stages[stage].const_buffers};
+ const auto& cbufs{maxwell3d->state.shader_stages[stage].const_buffers};
const auto read_handle{[&](const auto& desc, u32 index) {
ASSERT(cbufs[desc.cbuf_index].enabled);
const u32 index_offset{index << desc.size_shift};
@@ -316,13 +314,14 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
const u32 second_offset{desc.secondary_cbuf_offset + index_offset};
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].address +
second_offset};
- const u32 lhs_raw{gpu_memory.Read<u32>(addr)};
- const u32 rhs_raw{gpu_memory.Read<u32>(separate_addr)};
+ const u32 lhs_raw{gpu_memory->Read<u32>(addr) << desc.shift_left};
+ const u32 rhs_raw{gpu_memory->Read<u32>(separate_addr)
+ << desc.secondary_shift_left};
const u32 raw{lhs_raw | rhs_raw};
return TexturePair(raw, via_header_index);
}
}
- return TexturePair(gpu_memory.Read<u32>(addr), via_header_index);
+ return TexturePair(gpu_memory->Read<u32>(addr), via_header_index);
}};
const auto add_image{[&](const auto& desc, bool blacklist) LAMBDA_FORCEINLINE {
for (u32 index = 0; index < desc.count; ++index) {
@@ -560,7 +559,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
vertex_attributes.push_back({
.location = static_cast<u32>(index),
.binding = attribute.buffer,
- .format = MaxwellToVK::VertexFormat(attribute.Type(), attribute.Size()),
+ .format = MaxwellToVK::VertexFormat(device, attribute.Type(), attribute.Size()),
.offset = attribute.offset,
});
}
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
index a0c1d8f07..85602592b 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -63,22 +62,23 @@ class Device;
class PipelineStatistics;
class RenderPassCache;
class RescalingPushConstant;
-class VKScheduler;
-class VKUpdateDescriptorQueue;
+class Scheduler;
+class UpdateDescriptorQueue;
class GraphicsPipeline {
static constexpr size_t NUM_STAGES = Tegra::Engines::Maxwell3D::Regs::MaxShaderStage;
public:
- explicit GraphicsPipeline(
- Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory,
- VKScheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache,
- VideoCore::ShaderNotify* shader_notify, const Device& device,
- DescriptorPool& descriptor_pool, VKUpdateDescriptorQueue& update_descriptor_queue,
- Common::ThreadWorker* worker_thread, PipelineStatistics* pipeline_statistics,
- RenderPassCache& render_pass_cache, const GraphicsPipelineCacheKey& key,
- std::array<vk::ShaderModule, NUM_STAGES> stages,
- const std::array<const Shader::Info*, NUM_STAGES>& infos);
+ explicit GraphicsPipeline(Scheduler& scheduler, BufferCache& buffer_cache,
+ TextureCache& texture_cache, VideoCore::ShaderNotify* shader_notify,
+ const Device& device, DescriptorPool& descriptor_pool,
+ UpdateDescriptorQueue& update_descriptor_queue,
+ Common::ThreadWorker* worker_thread,
+ PipelineStatistics* pipeline_statistics,
+ RenderPassCache& render_pass_cache,
+ const GraphicsPipelineCacheKey& key,
+ std::array<vk::ShaderModule, NUM_STAGES> stages,
+ const std::array<const Shader::Info*, NUM_STAGES>& infos);
GraphicsPipeline& operator=(GraphicsPipeline&&) noexcept = delete;
GraphicsPipeline(GraphicsPipeline&&) noexcept = delete;
@@ -110,6 +110,11 @@ public:
return [](GraphicsPipeline* pl, bool is_indexed) { pl->ConfigureImpl<Spec>(is_indexed); };
}
+ void SetEngine(Tegra::Engines::Maxwell3D* maxwell3d_, Tegra::MemoryManager* gpu_memory_) {
+ maxwell3d = maxwell3d_;
+ gpu_memory = gpu_memory_;
+ }
+
private:
template <typename Spec>
void ConfigureImpl(bool is_indexed);
@@ -121,13 +126,13 @@ private:
void Validate();
const GraphicsPipelineCacheKey key;
- Tegra::Engines::Maxwell3D& maxwell3d;
- Tegra::MemoryManager& gpu_memory;
+ Tegra::Engines::Maxwell3D* maxwell3d;
+ Tegra::MemoryManager* gpu_memory;
const Device& device;
TextureCache& texture_cache;
BufferCache& buffer_cache;
- VKScheduler& scheduler;
- VKUpdateDescriptorQueue& update_descriptor_queue;
+ Scheduler& scheduler;
+ UpdateDescriptorQueue& update_descriptor_queue;
void (*configure_func)(GraphicsPipeline*, bool){};
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
index 6852c11b0..4e81d3d28 100644
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h
index 9be9c9bed..362ed579a 100644
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.h
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index a633b73e5..732e7b6f2 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstddef>
@@ -16,13 +15,11 @@
#include "common/microprofile.h"
#include "common/thread_worker.h"
#include "core/core.h"
-#include "core/memory.h"
#include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/environment.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"
#include "shader_recompiler/frontend/maxwell/translate_program.h"
#include "shader_recompiler/program_header.h"
-#include "video_core/dirty_flags.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
@@ -56,7 +53,7 @@ using VideoCommon::FileEnvironment;
using VideoCommon::GenericEnvironment;
using VideoCommon::GraphicsEnvironment;
-constexpr u32 CACHE_VERSION = 5;
+constexpr u32 CACHE_VERSION = 6;
template <typename Container>
auto MakeSpan(Container& container) {
@@ -177,7 +174,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
case Maxwell::TessellationPrimitive::Quads:
return Shader::TessPrimitive::Quads;
}
- UNREACHABLE();
+ ASSERT(false);
return Shader::TessPrimitive::Triangles;
}();
info.tess_spacing = [&] {
@@ -190,7 +187,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
case Maxwell::TessellationSpacing::FractionalEven:
return Shader::TessSpacing::FractionalEven;
}
- UNREACHABLE();
+ ASSERT(false);
return Shader::TessSpacing::Equal;
}();
break;
@@ -262,20 +259,18 @@ bool GraphicsPipelineCacheKey::operator==(const GraphicsPipelineCacheKey& rhs) c
return std::memcmp(&rhs, this, Size()) == 0;
}
-PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, Tegra::Engines::Maxwell3D& maxwell3d_,
- Tegra::Engines::KeplerCompute& kepler_compute_,
- Tegra::MemoryManager& gpu_memory_, const Device& device_,
- VKScheduler& scheduler_, DescriptorPool& descriptor_pool_,
- VKUpdateDescriptorQueue& update_descriptor_queue_,
+PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device_,
+ Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
+ UpdateDescriptorQueue& update_descriptor_queue_,
RenderPassCache& render_pass_cache_, BufferCache& buffer_cache_,
TextureCache& texture_cache_, VideoCore::ShaderNotify& shader_notify_)
- : VideoCommon::ShaderCache{rasterizer_, gpu_memory_, maxwell3d_, kepler_compute_},
- device{device_}, scheduler{scheduler_}, descriptor_pool{descriptor_pool_},
- update_descriptor_queue{update_descriptor_queue_}, render_pass_cache{render_pass_cache_},
- buffer_cache{buffer_cache_}, texture_cache{texture_cache_}, shader_notify{shader_notify_},
+ : VideoCommon::ShaderCache{rasterizer_}, device{device_}, scheduler{scheduler_},
+ descriptor_pool{descriptor_pool_}, update_descriptor_queue{update_descriptor_queue_},
+ render_pass_cache{render_pass_cache_}, buffer_cache{buffer_cache_},
+ texture_cache{texture_cache_}, shader_notify{shader_notify_},
use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()},
- workers(std::max(std::thread::hardware_concurrency(), 2U) - 1, "yuzu:PipelineBuilder"),
- serialization_thread(1, "yuzu:PipelineSerialization") {
+ workers(std::max(std::thread::hardware_concurrency(), 2U) - 1, "VkPipelineBuilder"),
+ serialization_thread(1, "VkPipelineSerialization") {
const auto& float_control{device.FloatControlProperties()};
const VkDriverIdKHR driver_id{device.GetDriverID()};
profile = Shader::Profile{
@@ -340,7 +335,7 @@ GraphicsPipeline* PipelineCache::CurrentGraphicsPipeline() {
current_pipeline = nullptr;
return nullptr;
}
- graphics_key.state.Refresh(maxwell3d, device.IsExtExtendedDynamicStateSupported(),
+ graphics_key.state.Refresh(*maxwell3d, device.IsExtExtendedDynamicStateSupported(),
device.IsExtVertexInputDynamicStateSupported());
if (current_pipeline) {
@@ -360,7 +355,7 @@ ComputePipeline* PipelineCache::CurrentComputePipeline() {
if (!shader) {
return nullptr;
}
- const auto& qmd{kepler_compute.launch_description};
+ const auto& qmd{kepler_compute->launch_description};
const ComputePipelineCacheKey key{
.unique_hash = shader->unique_hash,
.shared_memory_size = qmd.shared_alloc,
@@ -406,7 +401,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));
}
@@ -436,8 +431,10 @@ 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};
- graphics_cache.emplace(key, std::move(pipeline));
+ std::scoped_lock lock{state.mutex};
+ if (pipeline) {
+ graphics_cache.emplace(key, std::move(pipeline));
+ }
++state.built;
if (state.has_loaded) {
callback(VideoCore::LoadCallbackStage::Build, state.built, state.total);
@@ -455,7 +452,7 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
state.has_loaded = true;
lock.unlock();
- workers.WaitForRequests();
+ workers.WaitForRequests(stop_loading);
if (state.statistics) {
state.statistics->Report();
@@ -487,13 +484,13 @@ GraphicsPipeline* PipelineCache::BuiltPipeline(GraphicsPipeline* pipeline) const
}
// If something is using depth, we can assume that games are not rendering anything which
// will be used one time.
- if (maxwell3d.regs.zeta_enable) {
+ if (maxwell3d->regs.zeta_enable) {
return nullptr;
}
// If games are using a small index count, we can assume these are full screen quads.
// Usually these shaders are only used once for building textures so we can assume they
// can't be built async
- if (maxwell3d.regs.index_array.count <= 6 || maxwell3d.regs.vertex_buffer.count <= 6) {
+ if (maxwell3d->regs.index_array.count <= 6 || maxwell3d->regs.vertex_buffer.count <= 6) {
return pipeline;
}
return nullptr;
@@ -558,10 +555,10 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
previous_stage = &program;
}
Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr};
- return std::make_unique<GraphicsPipeline>(
- maxwell3d, gpu_memory, scheduler, buffer_cache, texture_cache, &shader_notify, device,
- descriptor_pool, update_descriptor_queue, thread_worker, statistics, render_pass_cache, key,
- std::move(modules), infos);
+ return std::make_unique<GraphicsPipeline>(scheduler, buffer_cache, texture_cache,
+ &shader_notify, device, descriptor_pool,
+ update_descriptor_queue, thread_worker, statistics,
+ render_pass_cache, key, std::move(modules), infos);
} catch (const Shader::Exception& exception) {
LOG_ERROR(Render_Vulkan, "{}", exception.what());
@@ -593,9 +590,9 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() {
std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
const ComputePipelineCacheKey& key, const ShaderInfo* shader) {
- const GPUVAddr program_base{kepler_compute.regs.code_loc.Address()};
- const auto& qmd{kepler_compute.launch_description};
- ComputeEnvironment env{kepler_compute, gpu_memory, program_base, qmd.program_start};
+ const GPUVAddr program_base{kepler_compute->regs.code_loc.Address()};
+ const auto& qmd{kepler_compute->launch_description};
+ ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start};
env.SetCachedSize(shader->size_bytes);
main_pools.ReleaseContents();
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
index 4c135b5dd..61f9e9366 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
@@ -1,17 +1,14 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
#include <filesystem>
-#include <iosfwd>
#include <memory>
#include <type_traits>
#include <unordered_map>
-#include <utility>
#include <vector>
#include "common/common_types.h"
@@ -29,7 +26,6 @@
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/shader_cache.h"
-#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Core {
class System;
@@ -85,8 +81,8 @@ class Device;
class PipelineStatistics;
class RasterizerVulkan;
class RenderPassCache;
-class VKScheduler;
-class VKUpdateDescriptorQueue;
+class Scheduler;
+class UpdateDescriptorQueue;
using VideoCommon::ShaderInfo;
@@ -104,11 +100,9 @@ struct ShaderPools {
class PipelineCache : public VideoCommon::ShaderCache {
public:
- explicit PipelineCache(RasterizerVulkan& rasterizer, Tegra::Engines::Maxwell3D& maxwell3d,
- Tegra::Engines::KeplerCompute& kepler_compute,
- Tegra::MemoryManager& gpu_memory, const Device& device,
- VKScheduler& scheduler, DescriptorPool& descriptor_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue,
+ explicit PipelineCache(RasterizerVulkan& rasterizer, const Device& device, Scheduler& scheduler,
+ DescriptorPool& descriptor_pool,
+ UpdateDescriptorQueue& update_descriptor_queue,
RenderPassCache& render_pass_cache, BufferCache& buffer_cache,
TextureCache& texture_cache, VideoCore::ShaderNotify& shader_notify_);
~PipelineCache();
@@ -142,9 +136,9 @@ private:
bool build_in_parallel);
const Device& device;
- VKScheduler& scheduler;
+ Scheduler& scheduler;
DescriptorPool& descriptor_pool;
- VKUpdateDescriptorQueue& update_descriptor_queue;
+ UpdateDescriptorQueue& update_descriptor_queue;
RenderPassCache& render_pass_cache;
BufferCache& buffer_cache;
TextureCache& texture_cache;
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
index 259cba156..7cb02631c 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstddef>
@@ -27,7 +26,7 @@ constexpr VkQueryType GetTarget(QueryType type) {
} // Anonymous namespace
-QueryPool::QueryPool(const Device& device_, VKScheduler& scheduler, QueryType type_)
+QueryPool::QueryPool(const Device& device_, Scheduler& scheduler, QueryType type_)
: ResourcePool{scheduler.GetMasterSemaphore(), GROW_STEP}, device{device_}, type{type_} {}
QueryPool::~QueryPool() = default;
@@ -66,15 +65,14 @@ void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) {
usage[pool_index * GROW_STEP + static_cast<std::ptrdiff_t>(query.second)] = false;
}
-VKQueryCache::VKQueryCache(VideoCore::RasterizerInterface& rasterizer_,
- Tegra::Engines::Maxwell3D& maxwell3d_, Tegra::MemoryManager& gpu_memory_,
- const Device& device_, VKScheduler& scheduler_)
- : QueryCacheBase{rasterizer_, maxwell3d_, gpu_memory_}, device{device_}, scheduler{scheduler_},
+QueryCache::QueryCache(VideoCore::RasterizerInterface& rasterizer_, const Device& device_,
+ Scheduler& scheduler_)
+ : QueryCacheBase{rasterizer_}, device{device_}, scheduler{scheduler_},
query_pools{
QueryPool{device_, scheduler_, QueryType::SamplesPassed},
} {}
-VKQueryCache::~VKQueryCache() {
+QueryCache::~QueryCache() {
// TODO(Rodrigo): This is a hack to destroy all HostCounter instances before the base class
// destructor is called. The query cache should be redesigned to have a proper ownership model
// instead of using shared pointers.
@@ -85,15 +83,15 @@ VKQueryCache::~VKQueryCache() {
}
}
-std::pair<VkQueryPool, u32> VKQueryCache::AllocateQuery(QueryType type) {
+std::pair<VkQueryPool, u32> QueryCache::AllocateQuery(QueryType type) {
return query_pools[static_cast<std::size_t>(type)].Commit();
}
-void VKQueryCache::Reserve(QueryType type, std::pair<VkQueryPool, u32> query) {
+void QueryCache::Reserve(QueryType type, std::pair<VkQueryPool, u32> query) {
query_pools[static_cast<std::size_t>(type)].Reserve(query);
}
-HostCounter::HostCounter(VKQueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
+HostCounter::HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
QueryType type_)
: HostCounterBase{std::move(dependency_)}, cache{cache_}, type{type_},
query{cache_.AllocateQuery(type_)}, tick{cache_.GetScheduler().CurrentTick()} {
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h
index 7190946b9..26762ee09 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.h
+++ b/src/video_core/renderer_vulkan/vk_query_cache.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -23,14 +22,14 @@ namespace Vulkan {
class CachedQuery;
class Device;
class HostCounter;
-class VKQueryCache;
-class VKScheduler;
+class QueryCache;
+class Scheduler;
-using CounterStream = VideoCommon::CounterStreamBase<VKQueryCache, HostCounter>;
+using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
class QueryPool final : public ResourcePool {
public:
- explicit QueryPool(const Device& device, VKScheduler& scheduler, VideoCore::QueryType type);
+ explicit QueryPool(const Device& device, Scheduler& scheduler, VideoCore::QueryType type);
~QueryPool() override;
std::pair<VkQueryPool, u32> Commit();
@@ -50,13 +49,12 @@ private:
std::vector<bool> usage;
};
-class VKQueryCache final
- : public VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter> {
+class QueryCache final
+ : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> {
public:
- explicit VKQueryCache(VideoCore::RasterizerInterface& rasterizer_,
- Tegra::Engines::Maxwell3D& maxwell3d_, Tegra::MemoryManager& gpu_memory_,
- const Device& device_, VKScheduler& scheduler_);
- ~VKQueryCache();
+ explicit QueryCache(VideoCore::RasterizerInterface& rasterizer_, const Device& device_,
+ Scheduler& scheduler_);
+ ~QueryCache();
std::pair<VkQueryPool, u32> AllocateQuery(VideoCore::QueryType type);
@@ -66,19 +64,19 @@ public:
return device;
}
- VKScheduler& GetScheduler() const noexcept {
+ Scheduler& GetScheduler() const noexcept {
return scheduler;
}
private:
const Device& device;
- VKScheduler& scheduler;
+ Scheduler& scheduler;
std::array<QueryPool, VideoCore::NumQueryTypes> query_pools;
};
-class HostCounter final : public VideoCommon::HostCounterBase<VKQueryCache, HostCounter> {
+class HostCounter final : public VideoCommon::HostCounterBase<QueryCache, HostCounter> {
public:
- explicit HostCounter(VKQueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
+ explicit HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
VideoCore::QueryType type_);
~HostCounter();
@@ -87,7 +85,7 @@ public:
private:
u64 BlockingQuery() const override;
- VKQueryCache& cache;
+ QueryCache& cache;
const VideoCore::QueryType type;
const std::pair<VkQueryPool, u32> query;
const u64 tick;
@@ -95,7 +93,7 @@ private:
class CachedQuery : public VideoCommon::CachedQueryBase<HostCounter> {
public:
- explicit CachedQuery(VKQueryCache&, VideoCore::QueryType, VAddr cpu_addr_, u8* host_ptr_)
+ explicit CachedQuery(QueryCache&, VideoCore::QueryType, VAddr cpu_addr_, u8* host_ptr_)
: CachedQueryBase{cpu_addr_, host_ptr_} {}
};
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 2227d9197..acfd5da7d 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -1,20 +1,17 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <memory>
#include <mutex>
-#include <vector>
-#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/scope_exit.h"
#include "common/settings.h"
-#include "core/core.h"
+#include "video_core/control/channel_state.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/blit_image.h"
@@ -73,10 +70,17 @@ VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t in
const float width = conv(src.scale_x * 2.0f);
float y = conv(src.translate_y - src.scale_y);
float height = conv(src.scale_y * 2.0f);
- if (regs.screen_y_control.y_negate) {
+ bool y_negate = regs.screen_y_control.y_negate;
+
+ if (!device.IsNvViewportSwizzleSupported()) {
+ y_negate = y_negate != (src.swizzle.y == Maxwell::ViewportSwizzle::NegativeY);
+ }
+
+ if (y_negate) {
y += height;
height = -height;
}
+
const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f;
VkViewport viewport{
.x = x,
@@ -145,14 +149,11 @@ DrawParams MakeDrawParams(const Maxwell& regs, u32 num_instances, bool is_instan
} // Anonymous namespace
RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
- Tegra::MemoryManager& gpu_memory_,
- Core::Memory::Memory& cpu_memory_, VKScreenInfo& screen_info_,
+ Core::Memory::Memory& cpu_memory_, ScreenInfo& screen_info_,
const Device& device_, MemoryAllocator& memory_allocator_,
- StateTracker& state_tracker_, VKScheduler& scheduler_)
- : RasterizerAccelerated{cpu_memory_}, gpu{gpu_},
- gpu_memory{gpu_memory_}, maxwell3d{gpu.Maxwell3D()}, kepler_compute{gpu.KeplerCompute()},
- screen_info{screen_info_}, device{device_}, memory_allocator{memory_allocator_},
- state_tracker{state_tracker_}, scheduler{scheduler_},
+ StateTracker& state_tracker_, Scheduler& scheduler_)
+ : RasterizerAccelerated{cpu_memory_}, gpu{gpu_}, screen_info{screen_info_}, device{device_},
+ memory_allocator{memory_allocator_}, state_tracker{state_tracker_}, scheduler{scheduler_},
staging_pool(device, memory_allocator, scheduler), descriptor_pool(device, scheduler),
update_descriptor_queue(device, scheduler),
blit_image(device, scheduler, state_tracker, descriptor_pool),
@@ -162,14 +163,13 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
memory_allocator, staging_pool,
blit_image, astc_decoder_pass,
render_pass_cache},
- texture_cache(texture_cache_runtime, *this, maxwell3d, kepler_compute, gpu_memory),
+ texture_cache(texture_cache_runtime, *this),
buffer_cache_runtime(device, memory_allocator, scheduler, staging_pool,
update_descriptor_queue, descriptor_pool),
- buffer_cache(*this, maxwell3d, kepler_compute, gpu_memory, cpu_memory_, buffer_cache_runtime),
- pipeline_cache(*this, maxwell3d, kepler_compute, gpu_memory, device, scheduler,
- descriptor_pool, update_descriptor_queue, render_pass_cache, buffer_cache,
- texture_cache, gpu.ShaderNotify()),
- query_cache{*this, maxwell3d, gpu_memory, device, scheduler}, accelerate_dma{buffer_cache},
+ buffer_cache(*this, cpu_memory_, buffer_cache_runtime),
+ pipeline_cache(*this, device, scheduler, descriptor_pool, update_descriptor_queue,
+ render_pass_cache, buffer_cache, texture_cache, gpu.ShaderNotify()),
+ query_cache{*this, device, scheduler}, accelerate_dma{buffer_cache},
fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler),
wfi_event(device.GetLogical().CreateEvent()) {
scheduler.SetQueryCache(query_cache);
@@ -190,14 +190,16 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
return;
}
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+ // update engine as channel may be different.
+ pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->Configure(is_indexed);
BeginTransformFeedback();
UpdateDynamicStates();
- const auto& regs{maxwell3d.regs};
- const u32 num_instances{maxwell3d.mme_draw.instance_count};
+ const auto& regs{maxwell3d->regs};
+ const u32 num_instances{maxwell3d->mme_draw.instance_count};
const DrawParams draw_params{MakeDrawParams(regs, num_instances, is_instanced, is_indexed)};
scheduler.Record([draw_params](vk::CommandBuffer cmdbuf) {
if (draw_params.is_indexed) {
@@ -215,14 +217,14 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
void RasterizerVulkan::Clear() {
MICROPROFILE_SCOPE(Vulkan_Clearing);
- if (!maxwell3d.ShouldExecute()) {
+ if (!maxwell3d->ShouldExecute()) {
return;
}
FlushWork();
query_cache.UpdateCounters();
- auto& regs = maxwell3d.regs;
+ auto& regs = maxwell3d->regs;
const bool use_color = regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B ||
regs.clear_buffers.A;
const bool use_depth = regs.clear_buffers.Z;
@@ -245,8 +247,15 @@ void RasterizerVulkan::Clear() {
}
UpdateViewportsState(regs);
+ VkRect2D default_scissor;
+ default_scissor.offset.x = 0;
+ default_scissor.offset.y = 0;
+ default_scissor.extent.width = std::numeric_limits<s32>::max();
+ default_scissor.extent.height = std::numeric_limits<s32>::max();
+
VkClearRect clear_rect{
- .rect = GetScissorState(regs, 0, up_scale, down_shift),
+ .rect = regs.clear_flags.scissor ? GetScissorState(regs, 0, up_scale, down_shift)
+ : default_scissor,
.baseArrayLayer = regs.clear_buffers.layer,
.layerCount = 1,
};
@@ -336,9 +345,9 @@ void RasterizerVulkan::DispatchCompute() {
return;
}
std::scoped_lock lock{texture_cache.mutex, buffer_cache.mutex};
- pipeline->Configure(kepler_compute, gpu_memory, scheduler, buffer_cache, texture_cache);
+ pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache);
- const auto& qmd{kepler_compute.launch_description};
+ const auto& qmd{kepler_compute->launch_description};
const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); });
@@ -419,7 +428,7 @@ void RasterizerVulkan::OnCPUWrite(VAddr addr, u64 size) {
}
}
-void RasterizerVulkan::SyncGuestHost() {
+void RasterizerVulkan::InvalidateGPUCache() {
pipeline_cache.SyncGuestHost();
{
std::scoped_lock lock{buffer_cache.mutex};
@@ -439,40 +448,30 @@ void RasterizerVulkan::UnmapMemory(VAddr addr, u64 size) {
pipeline_cache.OnCPUWrite(addr, size);
}
-void RasterizerVulkan::ModifyGPUMemory(GPUVAddr addr, u64 size) {
+void RasterizerVulkan::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {
{
std::scoped_lock lock{texture_cache.mutex};
- texture_cache.UnmapGPUMemory(addr, size);
+ texture_cache.UnmapGPUMemory(as_id, addr, size);
}
}
-void RasterizerVulkan::SignalSemaphore(GPUVAddr addr, u32 value) {
- if (!gpu.IsAsync()) {
- gpu_memory.Write<u32>(addr, value);
- return;
- }
- fence_manager.SignalSemaphore(addr, value);
+void RasterizerVulkan::SignalFence(std::function<void()>&& func) {
+ fence_manager.SignalFence(std::move(func));
+}
+
+void RasterizerVulkan::SyncOperation(std::function<void()>&& func) {
+ fence_manager.SyncOperation(std::move(func));
}
void RasterizerVulkan::SignalSyncPoint(u32 value) {
- if (!gpu.IsAsync()) {
- gpu.IncrementSyncPoint(value);
- return;
- }
fence_manager.SignalSyncPoint(value);
}
void RasterizerVulkan::SignalReference() {
- if (!gpu.IsAsync()) {
- return;
- }
fence_manager.SignalOrdering();
}
void RasterizerVulkan::ReleaseFences() {
- if (!gpu.IsAsync()) {
- return;
- }
fence_manager.WaitPendingFences();
}
@@ -549,13 +548,13 @@ Tegra::Engines::AccelerateDMAInterface& RasterizerVulkan::AccessAccelerateDMA()
}
void RasterizerVulkan::AccelerateInlineToMemory(GPUVAddr address, size_t copy_size,
- std::span<u8> memory) {
- auto cpu_addr = gpu_memory.GpuToCpuAddress(address);
+ std::span<const u8> memory) {
+ auto cpu_addr = gpu_memory->GpuToCpuAddress(address);
if (!cpu_addr) [[unlikely]] {
- gpu_memory.WriteBlock(address, memory.data(), copy_size);
+ gpu_memory->WriteBlock(address, memory.data(), copy_size);
return;
}
- gpu_memory.WriteBlockUnsafe(address, memory.data(), copy_size);
+ gpu_memory->WriteBlockUnsafe(address, memory.data(), copy_size);
{
std::unique_lock<std::mutex> lock{buffer_cache.mutex};
if (!buffer_cache.InlineMemory(*cpu_addr, copy_size, memory)) {
@@ -624,7 +623,7 @@ bool AccelerateDMA::BufferCopy(GPUVAddr src_address, GPUVAddr dest_address, u64
}
void RasterizerVulkan::UpdateDynamicStates() {
- auto& regs = maxwell3d.regs;
+ auto& regs = maxwell3d->regs;
UpdateViewportsState(regs);
UpdateScissorsState(regs);
UpdateDepthBias(regs);
@@ -648,7 +647,7 @@ void RasterizerVulkan::UpdateDynamicStates() {
}
void RasterizerVulkan::BeginTransformFeedback() {
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
if (regs.tfb_enabled == 0) {
return;
}
@@ -664,7 +663,7 @@ void RasterizerVulkan::BeginTransformFeedback() {
}
void RasterizerVulkan::EndTransformFeedback() {
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
if (regs.tfb_enabled == 0) {
return;
}
@@ -788,8 +787,8 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs)
});
} else {
// Front face defines both faces
- scheduler.Record([ref = regs.stencil_back_func_ref, write_mask = regs.stencil_back_mask,
- test_mask = regs.stencil_back_func_mask](vk::CommandBuffer cmdbuf) {
+ scheduler.Record([ref = regs.stencil_front_func_ref, write_mask = regs.stencil_front_mask,
+ test_mask = regs.stencil_front_func_mask](vk::CommandBuffer cmdbuf) {
cmdbuf.SetStencilReference(VK_STENCIL_FACE_FRONT_AND_BACK, ref);
cmdbuf.SetStencilWriteMask(VK_STENCIL_FACE_FRONT_AND_BACK, write_mask);
cmdbuf.SetStencilCompareMask(VK_STENCIL_FACE_FRONT_AND_BACK, test_mask);
@@ -914,7 +913,7 @@ void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs&
}
void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs) {
- auto& dirty{maxwell3d.dirty.flags};
+ auto& dirty{maxwell3d->dirty.flags};
if (!dirty[Dirty::VertexInput]) {
return;
}
@@ -943,7 +942,7 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
.pNext = nullptr,
.location = static_cast<u32>(index),
.binding = binding,
- .format = MaxwellToVK::VertexFormat(attribute.type, attribute.size),
+ .format = MaxwellToVK::VertexFormat(device, attribute.type, attribute.size),
.offset = attribute.offset,
});
}
@@ -971,4 +970,41 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
});
}
+void RasterizerVulkan::InitializeChannel(Tegra::Control::ChannelState& channel) {
+ CreateChannel(channel);
+ {
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+ texture_cache.CreateChannel(channel);
+ buffer_cache.CreateChannel(channel);
+ }
+ pipeline_cache.CreateChannel(channel);
+ query_cache.CreateChannel(channel);
+ state_tracker.SetupTables(channel);
+}
+
+void RasterizerVulkan::BindChannel(Tegra::Control::ChannelState& channel) {
+ const s32 channel_id = channel.bind_id;
+ BindToChannel(channel_id);
+ {
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+ texture_cache.BindToChannel(channel_id);
+ buffer_cache.BindToChannel(channel_id);
+ }
+ pipeline_cache.BindToChannel(channel_id);
+ query_cache.BindToChannel(channel_id);
+ state_tracker.ChangeChannel(channel);
+ state_tracker.InvalidateState();
+}
+
+void RasterizerVulkan::ReleaseChannel(s32 channel_id) {
+ EraseChannel(channel_id);
+ {
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+ texture_cache.EraseChannel(channel_id);
+ buffer_cache.EraseChannel(channel_id);
+ }
+ pipeline_cache.EraseChannel(channel_id);
+ query_cache.EraseChannel(channel_id);
+}
+
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 5af2e275b..4cde3c983 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -1,30 +1,24 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
-#include <bitset>
-#include <memory>
-#include <utility>
-#include <vector>
#include <boost/container/static_vector.hpp>
#include "common/common_types.h"
+#include "video_core/control/channel_state_cache.h"
#include "video_core/engines/maxwell_dma.h"
#include "video_core/rasterizer_accelerated.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_vulkan/blit_image.h"
-#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_fence_manager.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
#include "video_core/renderer_vulkan/vk_query_cache.h"
#include "video_core/renderer_vulkan/vk_render_pass_cache.h"
-#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
@@ -45,7 +39,7 @@ class Maxwell3D;
namespace Vulkan {
-struct VKScreenInfo;
+struct ScreenInfo;
class StateTracker;
@@ -61,13 +55,13 @@ private:
BufferCache& buffer_cache;
};
-class RasterizerVulkan final : public VideoCore::RasterizerAccelerated {
+class RasterizerVulkan final : public VideoCore::RasterizerAccelerated,
+ protected VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
public:
explicit RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
- Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
- VKScreenInfo& screen_info_, const Device& device_,
- MemoryAllocator& memory_allocator_, StateTracker& state_tracker_,
- VKScheduler& scheduler_);
+ Core::Memory::Memory& cpu_memory_, ScreenInfo& screen_info_,
+ const Device& device_, MemoryAllocator& memory_allocator_,
+ StateTracker& state_tracker_, Scheduler& scheduler_);
~RasterizerVulkan() override;
void Draw(bool is_indexed, bool is_instanced) override;
@@ -82,10 +76,11 @@ public:
bool MustFlushRegion(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size) override;
void OnCPUWrite(VAddr addr, u64 size) override;
- void SyncGuestHost() override;
+ void InvalidateGPUCache() override;
void UnmapMemory(VAddr addr, u64 size) override;
- void ModifyGPUMemory(GPUVAddr addr, u64 size) override;
- void SignalSemaphore(GPUVAddr addr, u32 value) override;
+ void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override;
+ void SignalFence(std::function<void()>&& func) override;
+ void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override;
void SignalReference() override;
void ReleaseFences() override;
@@ -100,12 +95,18 @@ public:
const Tegra::Engines::Fermi2D::Config& copy_config) override;
Tegra::Engines::AccelerateDMAInterface& AccessAccelerateDMA() override;
void AccelerateInlineToMemory(GPUVAddr address, size_t copy_size,
- std::span<u8> memory) override;
+ std::span<const u8> memory) override;
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) override;
+ void InitializeChannel(Tegra::Control::ChannelState& channel) override;
+
+ void BindChannel(Tegra::Control::ChannelState& channel) override;
+
+ void ReleaseChannel(s32 channel_id) override;
+
private:
static constexpr size_t MAX_TEXTURES = 192;
static constexpr size_t MAX_IMAGES = 48;
@@ -141,19 +142,16 @@ private:
void UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs);
Tegra::GPU& gpu;
- Tegra::MemoryManager& gpu_memory;
- Tegra::Engines::Maxwell3D& maxwell3d;
- Tegra::Engines::KeplerCompute& kepler_compute;
- VKScreenInfo& screen_info;
+ ScreenInfo& screen_info;
const Device& device;
MemoryAllocator& memory_allocator;
StateTracker& state_tracker;
- VKScheduler& scheduler;
+ Scheduler& scheduler;
StagingBufferPool staging_pool;
DescriptorPool descriptor_pool;
- VKUpdateDescriptorQueue update_descriptor_queue;
+ UpdateDescriptorQueue update_descriptor_queue;
BlitImageHelper blit_image;
ASTCDecoderPass astc_decoder_pass;
RenderPassCache render_pass_cache;
@@ -163,9 +161,9 @@ private:
BufferCacheRuntime buffer_cache_runtime;
BufferCache buffer_cache;
PipelineCache pipeline_cache;
- VKQueryCache query_cache;
+ QueryCache query_cache;
AccelerateDMA accelerate_dma;
- VKFenceManager fence_manager;
+ FenceManager fence_manager;
vk::Event wfi_event;
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..ae9f1de64 100644
--- a/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <unordered_map>
@@ -36,7 +35,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_render_pass_cache.h b/src/video_core/renderer_vulkan/vk_render_pass_cache.h
index eaa0ed775..dc21b7e69 100644
--- a/src/video_core/renderer_vulkan/vk_render_pass_cache.h
+++ b/src/video_core/renderer_vulkan/vk_render_pass_cache.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp
index 2dd514968..6c8ac22f4 100644
--- a/src/video_core/renderer_vulkan/vk_resource_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <optional>
diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.h b/src/video_core/renderer_vulkan/vk_resource_pool.h
index f0b80ad59..a39a3b881 100644
--- a/src/video_core/renderer_vulkan/vk_resource_pool.h
+++ b/src/video_core/renderer_vulkan/vk_resource_pool.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index 7d9d4f7ba..d96720b80 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -1,10 +1,8 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include <mutex>
-#include <optional>
#include <thread>
#include <utility>
@@ -23,7 +21,7 @@ namespace Vulkan {
MICROPROFILE_DECLARE(Vulkan_WaitForWorker);
-void VKScheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) {
+void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) {
auto command = first;
while (command != nullptr) {
auto next = command->GetNext();
@@ -37,7 +35,7 @@ void VKScheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) {
last = nullptr;
}
-VKScheduler::VKScheduler(const Device& device_, StateTracker& state_tracker_)
+Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_)
: device{device_}, state_tracker{state_tracker_},
master_semaphore{std::make_unique<MasterSemaphore>(device)},
command_pool{std::make_unique<CommandPool>(*master_semaphore, device)} {
@@ -46,14 +44,14 @@ VKScheduler::VKScheduler(const Device& device_, StateTracker& state_tracker_)
worker_thread = std::jthread([this](std::stop_token token) { WorkerThread(token); });
}
-VKScheduler::~VKScheduler() = default;
+Scheduler::~Scheduler() = default;
-void VKScheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
+void Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
SubmitExecution(signal_semaphore, wait_semaphore);
AllocateNewContext();
}
-void VKScheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
+void Scheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
const u64 presubmit_tick = CurrentTick();
SubmitExecution(signal_semaphore, wait_semaphore);
WaitWorker();
@@ -61,7 +59,7 @@ void VKScheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphor
AllocateNewContext();
}
-void VKScheduler::WaitWorker() {
+void Scheduler::WaitWorker() {
MICROPROFILE_SCOPE(Vulkan_WaitForWorker);
DispatchWork();
@@ -69,19 +67,19 @@ void VKScheduler::WaitWorker() {
wait_cv.wait(lock, [this] { return work_queue.empty(); });
}
-void VKScheduler::DispatchWork() {
+void Scheduler::DispatchWork() {
if (chunk->Empty()) {
return;
}
{
- std::lock_guard lock{work_mutex};
+ std::scoped_lock lock{work_mutex};
work_queue.push(std::move(chunk));
}
work_cv.notify_one();
AcquireNewChunk();
}
-void VKScheduler::RequestRenderpass(const Framebuffer* framebuffer) {
+void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) {
const VkRenderPass renderpass = framebuffer->RenderPass();
const VkFramebuffer framebuffer_handle = framebuffer->Handle();
const VkExtent2D render_area = framebuffer->RenderArea();
@@ -116,11 +114,11 @@ void VKScheduler::RequestRenderpass(const Framebuffer* framebuffer) {
renderpass_image_ranges = framebuffer->ImageRanges();
}
-void VKScheduler::RequestOutsideRenderPassOperationContext() {
+void Scheduler::RequestOutsideRenderPassOperationContext() {
EndRenderPass();
}
-bool VKScheduler::UpdateGraphicsPipeline(GraphicsPipeline* pipeline) {
+bool Scheduler::UpdateGraphicsPipeline(GraphicsPipeline* pipeline) {
if (state.graphics_pipeline == pipeline) {
return false;
}
@@ -128,7 +126,7 @@ bool VKScheduler::UpdateGraphicsPipeline(GraphicsPipeline* pipeline) {
return true;
}
-bool VKScheduler::UpdateRescaling(bool is_rescaling) {
+bool Scheduler::UpdateRescaling(bool is_rescaling) {
if (state.rescaling_defined && is_rescaling == state.is_rescaling) {
return false;
}
@@ -137,8 +135,8 @@ bool VKScheduler::UpdateRescaling(bool is_rescaling) {
return true;
}
-void VKScheduler::WorkerThread(std::stop_token stop_token) {
- Common::SetCurrentThreadName("yuzu:VulkanWorker");
+void Scheduler::WorkerThread(std::stop_token stop_token) {
+ Common::SetCurrentThreadName("VulkanWorker");
do {
std::unique_ptr<CommandChunk> work;
{
@@ -158,12 +156,12 @@ 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());
}
-void VKScheduler::AllocateWorkerCommandBuffer() {
+void Scheduler::AllocateWorkerCommandBuffer() {
current_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader());
current_cmdbuf.Begin({
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
@@ -173,7 +171,7 @@ void VKScheduler::AllocateWorkerCommandBuffer() {
});
}
-void VKScheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
+void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
EndPendingOperations();
InvalidateState();
@@ -227,25 +225,25 @@ void VKScheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait
DispatchWork();
}
-void VKScheduler::AllocateNewContext() {
+void Scheduler::AllocateNewContext() {
// Enable counters once again. These are disabled when a command buffer is finished.
if (query_cache) {
query_cache->UpdateCounters();
}
}
-void VKScheduler::InvalidateState() {
+void Scheduler::InvalidateState() {
state.graphics_pipeline = nullptr;
state.rescaling_defined = false;
state_tracker.InvalidateCommandBufferState();
}
-void VKScheduler::EndPendingOperations() {
+void Scheduler::EndPendingOperations() {
query_cache->DisableStreams();
EndRenderPass();
}
-void VKScheduler::EndRenderPass() {
+void Scheduler::EndRenderPass() {
if (!state.renderpass) {
return;
}
@@ -282,8 +280,8 @@ void VKScheduler::EndRenderPass() {
num_renderpass_images = 0;
}
-void VKScheduler::AcquireNewChunk() {
- std::lock_guard lock{reserve_mutex};
+void Scheduler::AcquireNewChunk() {
+ 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_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index e69aa136b..c04aad08f 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -1,10 +1,8 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <memory>
@@ -24,14 +22,14 @@ class Device;
class Framebuffer;
class GraphicsPipeline;
class StateTracker;
-class VKQueryCache;
+class QueryCache;
/// The scheduler abstracts command buffer and fence management with an interface that's able to do
/// OpenGL-like operations on Vulkan command buffers.
-class VKScheduler {
+class Scheduler {
public:
- explicit VKScheduler(const Device& device, StateTracker& state_tracker);
- ~VKScheduler();
+ explicit Scheduler(const Device& device, StateTracker& state_tracker);
+ ~Scheduler();
/// Sends the current execution context to the GPU.
void Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
@@ -63,7 +61,7 @@ public:
void InvalidateState();
/// Assigns the query cache.
- void SetQueryCache(VKQueryCache& query_cache_) {
+ void SetQueryCache(QueryCache& query_cache_) {
query_cache = &query_cache_;
}
@@ -214,7 +212,7 @@ private:
std::unique_ptr<MasterSemaphore> master_semaphore;
std::unique_ptr<CommandPool> command_pool;
- VKQueryCache* query_cache = nullptr;
+ QueryCache* query_cache = nullptr;
vk::CommandBuffer current_cmdbuf;
diff --git a/src/video_core/renderer_vulkan/vk_shader_util.cpp b/src/video_core/renderer_vulkan/vk_shader_util.cpp
index aaad4f292..7a0a2b154 100644
--- a/src/video_core/renderer_vulkan/vk_shader_util.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_util.cpp
@@ -1,11 +1,8 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
-#include <memory>
-#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/vulkan_common/vulkan_device.h"
diff --git a/src/video_core/renderer_vulkan/vk_shader_util.h b/src/video_core/renderer_vulkan/vk_shader_util.h
index 9517cbe84..2f7c9f25c 100644
--- a/src/video_core/renderer_vulkan/vk_shader_util.h
+++ b/src/video_core/renderer_vulkan/vk_shader_util.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
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..7fb256953 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <utility>
@@ -27,20 +26,39 @@ using namespace Common::Literals;
constexpr VkDeviceSize MAX_ALIGNMENT = 256;
// Maximum size to put elements in the stream buffer
constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8_MiB;
-// Stream buffer size in bytes
-constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128_MiB;
-constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS;
constexpr VkMemoryPropertyFlags HOST_FLAGS =
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
constexpr VkMemoryPropertyFlags STREAM_FLAGS = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | HOST_FLAGS;
-bool IsStreamHeap(VkMemoryHeap heap) noexcept {
- return STREAM_BUFFER_SIZE < (heap.size * 2) / 3;
+static bool IsStreamHeap(VkMemoryHeap heap, size_t staging_buffer_size) noexcept {
+ return staging_buffer_size < (heap.size * 2) / 3;
+}
+
+static bool HasLargeDeviceLocalHostVisibleMemory(const VkPhysicalDeviceMemoryProperties& props) {
+ const auto flags{VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT};
+
+ for (u32 type_index = 0; type_index < props.memoryTypeCount; ++type_index) {
+ const auto& memory_type{props.memoryTypes[type_index]};
+
+ if ((memory_type.propertyFlags & flags) != flags) {
+ // Memory must be device local and host visible
+ continue;
+ }
+
+ const auto& heap{props.memoryHeaps[memory_type.heapIndex]};
+ if (heap.size >= 7168_MiB) {
+ // This is the right type of memory
+ return true;
+ }
+ }
+
+ return false;
}
std::optional<u32> FindMemoryTypeIndex(const VkPhysicalDeviceMemoryProperties& props, u32 type_mask,
- VkMemoryPropertyFlags flags) noexcept {
+ VkMemoryPropertyFlags flags,
+ size_t staging_buffer_size) noexcept {
for (u32 type_index = 0; type_index < props.memoryTypeCount; ++type_index) {
if (((type_mask >> type_index) & 1) == 0) {
// Memory type is incompatible
@@ -51,7 +69,7 @@ std::optional<u32> FindMemoryTypeIndex(const VkPhysicalDeviceMemoryProperties& p
// Memory type doesn't have the flags we want
continue;
}
- if (!IsStreamHeap(props.memoryHeaps[memory_type.heapIndex])) {
+ if (!IsStreamHeap(props.memoryHeaps[memory_type.heapIndex], staging_buffer_size)) {
// Memory heap is not suitable for streaming
continue;
}
@@ -62,17 +80,17 @@ std::optional<u32> FindMemoryTypeIndex(const VkPhysicalDeviceMemoryProperties& p
}
u32 FindMemoryTypeIndex(const VkPhysicalDeviceMemoryProperties& props, u32 type_mask,
- bool try_device_local) {
+ bool try_device_local, size_t staging_buffer_size) {
std::optional<u32> type;
if (try_device_local) {
// Try to find a DEVICE_LOCAL_BIT type, Nvidia and AMD have a dedicated heap for this
- type = FindMemoryTypeIndex(props, type_mask, STREAM_FLAGS);
+ type = FindMemoryTypeIndex(props, type_mask, STREAM_FLAGS, staging_buffer_size);
if (type) {
return *type;
}
}
// Otherwise try without the DEVICE_LOCAL_BIT
- type = FindMemoryTypeIndex(props, type_mask, HOST_FLAGS);
+ type = FindMemoryTypeIndex(props, type_mask, HOST_FLAGS, staging_buffer_size);
if (type) {
return *type;
}
@@ -80,20 +98,32 @@ u32 FindMemoryTypeIndex(const VkPhysicalDeviceMemoryProperties& props, u32 type_
throw vk::Exception(VK_ERROR_OUT_OF_DEVICE_MEMORY);
}
-size_t Region(size_t iterator) noexcept {
- return iterator / REGION_SIZE;
+size_t Region(size_t iterator, size_t region_size) noexcept {
+ return iterator / region_size;
}
} // Anonymous namespace
StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
- VKScheduler& scheduler_)
+ Scheduler& scheduler_)
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} {
+
+ const auto memory_properties{device.GetPhysical().GetMemoryProperties().memoryProperties};
+ if (HasLargeDeviceLocalHostVisibleMemory(memory_properties)) {
+ // Possible on many integrated and newer discrete cards
+ staging_buffer_size = 1_GiB;
+ } else {
+ // Well-supported default size used by most Vulkan PC games
+ staging_buffer_size = 256_MiB;
+ }
+
+ region_size = staging_buffer_size / StagingBufferPool::NUM_SYNCS;
+
const vk::Device& dev = device.GetLogical();
stream_buffer = dev.CreateBuffer(VkBufferCreateInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
- .size = STREAM_BUFFER_SIZE,
+ .size = staging_buffer_size,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
@@ -118,19 +148,18 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem
.image = nullptr,
.buffer = *stream_buffer,
};
- const auto memory_properties = device.GetPhysical().GetMemoryProperties();
VkMemoryAllocateInfo stream_memory_info{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = make_dedicated ? &dedicated_info : nullptr,
.allocationSize = requirements.size,
- .memoryTypeIndex =
- FindMemoryTypeIndex(memory_properties, requirements.memoryTypeBits, true),
+ .memoryTypeIndex = FindMemoryTypeIndex(memory_properties, requirements.memoryTypeBits, true,
+ staging_buffer_size),
};
stream_memory = dev.TryAllocateMemory(stream_memory_info);
if (!stream_memory) {
LOG_INFO(Render_Vulkan, "Dynamic memory allocation failed, trying with system memory");
- stream_memory_info.memoryTypeIndex =
- FindMemoryTypeIndex(memory_properties, requirements.memoryTypeBits, false);
+ stream_memory_info.memoryTypeIndex = FindMemoryTypeIndex(
+ memory_properties, requirements.memoryTypeBits, false, staging_buffer_size);
stream_memory = dev.AllocateMemory(stream_memory_info);
}
@@ -138,7 +167,7 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem
stream_memory.SetObjectNameEXT("Stream Buffer Memory");
}
stream_buffer.BindMemory(*stream_memory, 0);
- stream_pointer = stream_memory.Map(0, STREAM_BUFFER_SIZE);
+ stream_pointer = stream_memory.Map(0, staging_buffer_size);
}
StagingBufferPool::~StagingBufferPool() = default;
@@ -159,25 +188,25 @@ void StagingBufferPool::TickFrame() {
}
StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
- if (AreRegionsActive(Region(free_iterator) + 1,
- std::min(Region(iterator + size) + 1, NUM_SYNCS))) {
+ if (AreRegionsActive(Region(free_iterator, region_size) + 1,
+ std::min(Region(iterator + size, region_size) + 1, NUM_SYNCS))) {
// Avoid waiting for the previous usages to be free
return GetStagingBuffer(size, MemoryUsage::Upload);
}
const u64 current_tick = scheduler.CurrentTick();
- std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + Region(iterator),
- current_tick);
+ std::fill(sync_ticks.begin() + Region(used_iterator, region_size),
+ sync_ticks.begin() + Region(iterator, region_size), current_tick);
used_iterator = iterator;
free_iterator = std::max(free_iterator, iterator + size);
- if (iterator + size >= STREAM_BUFFER_SIZE) {
- std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS,
- current_tick);
+ if (iterator + size >= staging_buffer_size) {
+ std::fill(sync_ticks.begin() + Region(used_iterator, region_size),
+ sync_ticks.begin() + NUM_SYNCS, current_tick);
used_iterator = 0;
iterator = 0;
free_iterator = size;
- if (AreRegionsActive(0, Region(size) + 1)) {
+ if (AreRegionsActive(0, Region(size, region_size) + 1)) {
// Avoid waiting for the previous usages to be free
return GetStagingBuffer(size, MemoryUsage::Upload);
}
@@ -264,7 +293,7 @@ StagingBufferPool::StagingBuffersCache& StagingBufferPool::GetCache(MemoryUsage
case MemoryUsage::Download:
return download_cache;
default:
- UNREACHABLE_MSG("Invalid memory usage={}", usage);
+ ASSERT_MSG(false, "Invalid memory usage={}", usage);
return upload_cache;
}
}
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
index 69f7618de..90c67177f 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -15,7 +14,7 @@
namespace Vulkan {
class Device;
-class VKScheduler;
+class Scheduler;
struct StagingBufferRef {
VkBuffer buffer;
@@ -28,7 +27,7 @@ public:
static constexpr size_t NUM_SYNCS = 16;
explicit StagingBufferPool(const Device& device, MemoryAllocator& memory_allocator,
- VKScheduler& scheduler);
+ Scheduler& scheduler);
~StagingBufferPool();
StagingBufferRef Request(size_t size, MemoryUsage usage);
@@ -83,7 +82,7 @@ private:
const Device& device;
MemoryAllocator& memory_allocator;
- VKScheduler& scheduler;
+ Scheduler& scheduler;
vk::Buffer stream_buffer;
vk::DeviceMemory stream_memory;
@@ -94,6 +93,9 @@ private:
size_t free_iterator = 0;
std::array<u64, NUM_SYNCS> sync_ticks{};
+ size_t staging_buffer_size = 0;
+ size_t region_size = 0;
+
StagingBuffersCache device_local_cache;
StagingBuffersCache upload_cache;
StagingBuffersCache download_cache;
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
index c00913f55..f234e1a31 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
@@ -1,17 +1,15 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cstddef>
-#include <iterator>
#include "common/common_types.h"
#include "core/core.h"
+#include "video_core/control/channel_state.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h"
-#include "video_core/gpu.h"
#include "video_core/renderer_vulkan/vk_state_tracker.h"
#define OFF(field_name) MAXWELL3D_REG_INDEX(field_name)
@@ -176,9 +174,8 @@ void SetupDirtyVertexBindings(Tables& tables) {
}
} // Anonymous namespace
-StateTracker::StateTracker(Tegra::GPU& gpu)
- : flags{gpu.Maxwell3D().dirty.flags}, invalidation_flags{MakeInvalidationFlags()} {
- auto& tables{gpu.Maxwell3D().dirty.tables};
+void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) {
+ auto& tables{channel_state.maxwell_3d->dirty.tables};
SetupDirtyFlags(tables);
SetupDirtyViewports(tables);
SetupDirtyScissors(tables);
@@ -201,4 +198,15 @@ StateTracker::StateTracker(Tegra::GPU& gpu)
SetupDirtyVertexBindings(tables);
}
+void StateTracker::ChangeChannel(Tegra::Control::ChannelState& channel_state) {
+ flags = &channel_state.maxwell_3d->dirty.flags;
+}
+
+void StateTracker::InvalidateState() {
+ flags->set();
+}
+
+StateTracker::StateTracker()
+ : flags{&default_flags}, default_flags{}, invalidation_flags{MakeInvalidationFlags()} {}
+
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h
index 40a149832..2296dea60 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.h
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,10 +7,15 @@
#include <limits>
#include "common/common_types.h"
-#include "core/core.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h"
+namespace Tegra {
+namespace Control {
+struct ChannelState;
+}
+} // namespace Tegra
+
namespace Vulkan {
namespace Dirty {
@@ -55,19 +59,19 @@ class StateTracker {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
public:
- explicit StateTracker(Tegra::GPU& gpu);
+ explicit StateTracker();
void InvalidateCommandBufferState() {
- flags |= invalidation_flags;
+ (*flags) |= invalidation_flags;
current_topology = INVALID_TOPOLOGY;
}
void InvalidateViewports() {
- flags[Dirty::Viewports] = true;
+ (*flags)[Dirty::Viewports] = true;
}
void InvalidateScissors() {
- flags[Dirty::Scissors] = true;
+ (*flags)[Dirty::Scissors] = true;
}
bool TouchViewports() {
@@ -141,16 +145,23 @@ public:
return has_changed;
}
+ void SetupTables(Tegra::Control::ChannelState& channel_state);
+
+ void ChangeChannel(Tegra::Control::ChannelState& channel_state);
+
+ void InvalidateState();
+
private:
static constexpr auto INVALID_TOPOLOGY = static_cast<Maxwell::PrimitiveTopology>(~0u);
bool Exchange(std::size_t id, bool new_value) const noexcept {
- const bool is_dirty = flags[id];
- flags[id] = new_value;
+ const bool is_dirty = (*flags)[id];
+ (*flags)[id] = new_value;
return is_dirty;
}
- Tegra::Engines::Maxwell3D::DirtyState::Flags& flags;
+ Tegra::Engines::Maxwell3D::DirtyState::Flags* flags;
+ Tegra::Engines::Maxwell3D::DirtyState::Flags default_flags;
Tegra::Engines::Maxwell3D::DirtyState::Flags invalidation_flags;
Maxwell::PrimitiveTopology current_topology = INVALID_TOPOLOGY;
};
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index 8972a6921..706d9ba74 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -1,17 +1,14 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <limits>
#include <vector>
-#include "common/assert.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
-#include "core/frontend/framebuffer_layout.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
#include "video_core/vulkan_common/vulkan_device.h"
@@ -36,12 +33,14 @@ VkSurfaceFormatKHR ChooseSwapSurfaceFormat(vk::Span<VkSurfaceFormatKHR> formats)
}
VkPresentModeKHR ChooseSwapPresentMode(vk::Span<VkPresentModeKHR> modes) {
- // Mailbox doesn't lock the application like fifo (vsync), prefer it
+ // Mailbox (triple buffering) doesn't lock the application like fifo (vsync),
+ // prefer it if vsync option is not selected
const auto found_mailbox = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_MAILBOX_KHR);
- if (found_mailbox != modes.end()) {
+ if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Borderless &&
+ found_mailbox != modes.end() && !Settings::values.use_vsync.GetValue()) {
return VK_PRESENT_MODE_MAILBOX_KHR;
}
- if (Settings::values.disable_fps_limit.GetValue()) {
+ if (!Settings::values.use_speed_limit.GetValue()) {
// FIFO present mode locks the framerate to the monitor's refresh rate,
// Find an alternative to surpass this limitation if FPS is unlocked.
const auto found_imm = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_IMMEDIATE_KHR);
@@ -67,15 +66,15 @@ VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, u32 wi
} // Anonymous namespace
-VKSwapchain::VKSwapchain(VkSurfaceKHR surface_, const Device& device_, VKScheduler& scheduler_,
- u32 width, u32 height, bool srgb)
+Swapchain::Swapchain(VkSurfaceKHR surface_, const Device& device_, Scheduler& scheduler_, u32 width,
+ u32 height, bool srgb)
: surface{surface_}, device{device_}, scheduler{scheduler_} {
Create(width, height, srgb);
}
-VKSwapchain::~VKSwapchain() = default;
+Swapchain::~Swapchain() = default;
-void VKSwapchain::Create(u32 width, u32 height, bool srgb) {
+void Swapchain::Create(u32 width, u32 height, bool srgb) {
is_outdated = false;
is_suboptimal = false;
@@ -96,7 +95,7 @@ void VKSwapchain::Create(u32 width, u32 height, bool srgb) {
resource_ticks.resize(image_count);
}
-void VKSwapchain::AcquireNextImage() {
+void Swapchain::AcquireNextImage() {
const VkResult result = device.GetLogical().AcquireNextImageKHR(
*swapchain, std::numeric_limits<u64>::max(), *present_semaphores[frame_index],
VK_NULL_HANDLE, &image_index);
@@ -117,7 +116,7 @@ void VKSwapchain::AcquireNextImage() {
resource_ticks[image_index] = scheduler.CurrentTick();
}
-void VKSwapchain::Present(VkSemaphore render_semaphore) {
+void Swapchain::Present(VkSemaphore render_semaphore) {
const auto present_queue{device.GetPresentQueue()};
const VkPresentInfoKHR present_info{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
@@ -148,8 +147,8 @@ void VKSwapchain::Present(VkSemaphore render_semaphore) {
}
}
-void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, u32 width,
- u32 height, bool srgb) {
+void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, u32 width, u32 height,
+ bool srgb) {
const auto physical_device{device.GetPhysical()};
const auto formats{physical_device.GetSurfaceFormatsKHR(surface)};
const auto present_modes{physical_device.GetSurfacePresentModesKHR(surface)};
@@ -158,8 +157,16 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities,
present_mode = ChooseSwapPresentMode(present_modes);
u32 requested_image_count{capabilities.minImageCount + 1};
- if (capabilities.maxImageCount > 0 && requested_image_count > capabilities.maxImageCount) {
- requested_image_count = capabilities.maxImageCount;
+ // Ensure Tripple buffering if possible.
+ if (capabilities.maxImageCount > 0) {
+ if (requested_image_count > capabilities.maxImageCount) {
+ requested_image_count = capabilities.maxImageCount;
+ } else {
+ requested_image_count =
+ std::max(requested_image_count, std::min(3U, capabilities.maxImageCount));
+ }
+ } else {
+ requested_image_count = std::max(requested_image_count, 3U);
}
VkSwapchainCreateInfoKHR swapchain_ci{
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
@@ -208,20 +215,20 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities,
extent = swapchain_ci.imageExtent;
current_srgb = srgb;
- current_fps_unlocked = Settings::values.disable_fps_limit.GetValue();
+ current_fps_unlocked = !Settings::values.use_speed_limit.GetValue();
images = swapchain.GetImages();
image_count = static_cast<u32>(images.size());
image_view_format = srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
}
-void VKSwapchain::CreateSemaphores() {
+void Swapchain::CreateSemaphores() {
present_semaphores.resize(image_count);
std::ranges::generate(present_semaphores,
[this] { return device.GetLogical().CreateSemaphore(); });
}
-void VKSwapchain::CreateImageViews() {
+void Swapchain::CreateImageViews() {
VkImageViewCreateInfo ci{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
@@ -253,7 +260,7 @@ void VKSwapchain::CreateImageViews() {
}
}
-void VKSwapchain::Destroy() {
+void Swapchain::Destroy() {
frame_index = 0;
present_semaphores.clear();
framebuffers.clear();
@@ -261,11 +268,11 @@ void VKSwapchain::Destroy() {
swapchain.reset();
}
-bool VKSwapchain::HasFpsUnlockChanged() const {
- return current_fps_unlocked != Settings::values.disable_fps_limit.GetValue();
+bool Swapchain::HasFpsUnlockChanged() const {
+ return current_fps_unlocked != !Settings::values.use_speed_limit.GetValue();
}
-bool VKSwapchain::NeedsPresentModeUpdate() const {
+bool Swapchain::NeedsPresentModeUpdate() const {
// Mailbox present mode is the ideal for all scenarios. If it is not available,
// A different present mode is needed to support unlocked FPS above the monitor's refresh rate.
return present_mode != VK_PRESENT_MODE_MAILBOX_KHR && HasFpsUnlockChanged();
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h
index 61a6d959e..111b3902d 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.h
+++ b/src/video_core/renderer_vulkan/vk_swapchain.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -16,13 +15,13 @@ struct FramebufferLayout;
namespace Vulkan {
class Device;
-class VKScheduler;
+class Scheduler;
-class VKSwapchain {
+class Swapchain {
public:
- explicit VKSwapchain(VkSurfaceKHR surface, const Device& device, VKScheduler& scheduler,
- u32 width, u32 height, bool srgb);
- ~VKSwapchain();
+ explicit Swapchain(VkSurfaceKHR surface, const Device& device, Scheduler& scheduler, u32 width,
+ u32 height, bool srgb);
+ ~Swapchain();
/// Creates (or recreates) the swapchain with a given size.
void Create(u32 width, u32 height, bool srgb);
@@ -95,7 +94,7 @@ private:
const VkSurfaceKHR surface;
const Device& device;
- VKScheduler& scheduler;
+ Scheduler& scheduler;
vk::SwapchainKHR swapchain;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 0ba56ff1e..305ad8aee 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -71,7 +70,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
case ImageType::Buffer:
break;
}
- UNREACHABLE_MSG("Invalid image type={}", type);
+ ASSERT_MSG(false, "Invalid image type={}", type);
return {};
}
@@ -88,7 +87,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
case 16:
return VK_SAMPLE_COUNT_16_BIT;
default:
- UNREACHABLE_MSG("Invalid number of samples={}", num_samples);
+ ASSERT_MSG(false, "Invalid number of samples={}", num_samples);
return VK_SAMPLE_COUNT_1_BIT;
}
}
@@ -108,7 +107,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
break;
default:
- UNREACHABLE_MSG("Invalid surface type");
+ ASSERT_MSG(false, "Invalid surface type");
}
}
if (info.storage) {
@@ -180,7 +179,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
case VideoCore::Surface::SurfaceType::DepthStencil:
return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
default:
- UNREACHABLE_MSG("Invalid surface type");
+ ASSERT_MSG(false, "Invalid surface type");
return VkImageAspectFlags{};
}
}
@@ -222,7 +221,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
case SwizzleSource::OneInt:
return VK_COMPONENT_SWIZZLE_ONE;
}
- UNREACHABLE_MSG("Invalid swizzle={}", swizzle);
+ ASSERT_MSG(false, "Invalid swizzle={}", swizzle);
return VK_COMPONENT_SWIZZLE_ZERO;
}
@@ -231,6 +230,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
case Shader::TextureType::Color1D:
return VK_IMAGE_VIEW_TYPE_1D;
case Shader::TextureType::Color2D:
+ case Shader::TextureType::Color2DRect:
return VK_IMAGE_VIEW_TYPE_2D;
case Shader::TextureType::ColorCube:
return VK_IMAGE_VIEW_TYPE_CUBE;
@@ -243,10 +243,10 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
case Shader::TextureType::ColorArrayCube:
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
case Shader::TextureType::Buffer:
- UNREACHABLE_MSG("Texture buffers can't be image views");
+ ASSERT_MSG(false, "Texture buffers can't be image views");
return VK_IMAGE_VIEW_TYPE_1D;
}
- UNREACHABLE_MSG("Invalid image view type={}", type);
+ ASSERT_MSG(false, "Invalid image view type={}", type);
return VK_IMAGE_VIEW_TYPE_2D;
}
@@ -255,6 +255,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
case VideoCommon::ImageViewType::e1D:
return VK_IMAGE_VIEW_TYPE_1D;
case VideoCommon::ImageViewType::e2D:
+ case VideoCommon::ImageViewType::Rect:
return VK_IMAGE_VIEW_TYPE_2D;
case VideoCommon::ImageViewType::Cube:
return VK_IMAGE_VIEW_TYPE_CUBE;
@@ -266,14 +267,11 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
case VideoCommon::ImageViewType::CubeArray:
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
- case VideoCommon::ImageViewType::Rect:
- UNIMPLEMENTED_MSG("Rect image view");
- return VK_IMAGE_VIEW_TYPE_2D;
case VideoCommon::ImageViewType::Buffer:
- UNREACHABLE_MSG("Texture buffers can't be image views");
+ ASSERT_MSG(false, "Texture buffers can't be image views");
return VK_IMAGE_VIEW_TYPE_1D;
}
- UNREACHABLE_MSG("Invalid image view type={}", type);
+ ASSERT_MSG(false, "Invalid image view type={}", type);
return VK_IMAGE_VIEW_TYPE_2D;
}
@@ -438,6 +436,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,12 +578,25 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
};
}
-[[nodiscard]] bool IsFormatFlipped(PixelFormat format) {
+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:
+ 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::G4R4_UNORM:
+ std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed);
+ break;
default:
- return false;
+ break;
}
}
@@ -606,11 +643,11 @@ struct RangedBarrierRange {
case Shader::ImageFormat::R32G32B32A32_UINT:
return VK_FORMAT_R32G32B32A32_UINT;
}
- UNREACHABLE_MSG("Invalid image format={}", format);
+ ASSERT_MSG(false, "Invalid image format={}", format);
return VK_FORMAT_R32_UINT;
}
-void BlitScale(VKScheduler& scheduler, VkImage src_image, VkImage dst_image, const ImageInfo& info,
+void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const ImageInfo& info,
VkImageAspectFlags aspect_mask, const Settings::ResolutionScalingInfo& resolution,
bool up_scaling = true) {
const bool is_2d = info.type == ImageType::e2D;
@@ -750,7 +787,7 @@ void BlitScale(VKScheduler& scheduler, VkImage src_image, VkImage dst_image, con
}
} // Anonymous namespace
-TextureCacheRuntime::TextureCacheRuntime(const Device& device_, VKScheduler& scheduler_,
+TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& scheduler_,
MemoryAllocator& memory_allocator_,
StagingBufferPool& staging_buffer_pool_,
BlitImageHelper& blit_image_helper_,
@@ -779,11 +816,6 @@ bool TextureCacheRuntime::ShouldReinterpret(Image& dst, Image& src) {
!device.IsExtShaderStencilExportSupported()) {
return true;
}
- if (VideoCore::Surface::GetFormatType(src.info.format) ==
- VideoCore::Surface::SurfaceType::DepthStencil &&
- !device.IsExtShaderStencilExportSupported()) {
- return true;
- }
if (dst.info.format == PixelFormat::D32_FLOAT_S8_UINT ||
src.info.format == PixelFormat::D32_FLOAT_S8_UINT) {
return true;
@@ -1068,6 +1100,9 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
if (src_view.format == PixelFormat::S8_UINT_D24_UNORM) {
return blit_image_helper.ConvertD24S8ToABGR8(dst, src_view);
}
+ if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) {
+ return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view);
+ }
break;
case PixelFormat::R32_FLOAT:
if (src_view.format == PixelFormat::D32_FLOAT) {
@@ -1189,6 +1224,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 +1246,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());
@@ -1430,22 +1474,23 @@ bool Image::BlitScaleHelper(bool scale_up) {
};
const VkExtent2D extent{
.width = std::max(scaled_width, info.size.width),
- .height = std::max(scaled_height, info.size.width),
+ .height = std::max(scaled_height, info.size.height),
};
auto* view_ptr = blit_view.get();
if (aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT) {
if (!blit_framebuffer) {
- blit_framebuffer = std::make_unique<Framebuffer>(*runtime, view_ptr, nullptr, extent);
+ blit_framebuffer =
+ std::make_unique<Framebuffer>(*runtime, view_ptr, nullptr, extent, scale_up);
}
const auto color_view = blit_view->Handle(Shader::TextureType::Color2D);
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);
+ blit_framebuffer =
+ std::make_unique<Framebuffer>(*runtime, nullptr, view_ptr, extent, scale_up);
}
runtime->blit_image_helper.BlitDepthStencil(blit_framebuffer.get(), blit_view->DepthView(),
blit_view->StencilView(), dst_region,
@@ -1488,9 +1533,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
};
if (!info.IsRenderTarget()) {
swizzle = info.Swizzle();
- if (IsFormatFlipped(format)) {
- 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);
}
@@ -1537,6 +1580,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
break;
case VideoCommon::ImageViewType::e2D:
case VideoCommon::ImageViewType::e2DArray:
+ case VideoCommon::ImageViewType::Rect:
create(TextureType::Color2D, 1);
create(TextureType::ColorArray2D, std::nullopt);
render_target = Handle(Shader::TextureType::ColorArray2D);
@@ -1550,11 +1594,8 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
create(TextureType::ColorCube, 6);
create(TextureType::ColorArrayCube, std::nullopt);
break;
- case VideoCommon::ImageViewType::Rect:
- UNIMPLEMENTED();
- break;
case VideoCommon::ImageViewType::Buffer:
- UNREACHABLE();
+ ASSERT(false);
break;
}
}
@@ -1576,6 +1617,9 @@ ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParam
ImageView::~ImageView() = default;
VkImageView ImageView::DepthView() {
+ if (!image_handle) {
+ return VK_NULL_HANDLE;
+ }
if (depth_view) {
return *depth_view;
}
@@ -1585,6 +1629,9 @@ VkImageView ImageView::DepthView() {
}
VkImageView ImageView::StencilView() {
+ if (!image_handle) {
+ return VK_NULL_HANDLE;
+ }
if (stencil_view) {
return *stencil_view;
}
@@ -1594,6 +1641,9 @@ VkImageView ImageView::StencilView() {
}
VkImageView ImageView::ColorView() {
+ if (!image_handle) {
+ return VK_NULL_HANDLE;
+ }
if (color_view) {
return *color_view;
}
@@ -1603,6 +1653,9 @@ VkImageView ImageView::ColorView() {
VkImageView ImageView::StorageView(Shader::TextureType texture_type,
Shader::ImageFormat image_format) {
+ if (!image_handle) {
+ return VK_NULL_HANDLE;
+ }
if (image_format == Shader::ImageFormat::Typeless) {
return Handle(texture_type);
}
@@ -1705,34 +1758,42 @@ Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM
.width = key.size.width,
.height = key.size.height,
}} {
- CreateFramebuffer(runtime, color_buffers, depth_buffer);
+ CreateFramebuffer(runtime, color_buffers, depth_buffer, key.is_rescaled);
if (runtime.device.HasDebuggingToolAttached()) {
framebuffer.SetObjectNameEXT(VideoCommon::Name(key).c_str());
}
}
Framebuffer::Framebuffer(TextureCacheRuntime& runtime, ImageView* color_buffer,
- ImageView* depth_buffer, VkExtent2D extent)
+ ImageView* depth_buffer, VkExtent2D extent, bool is_rescaled)
: render_area{extent} {
std::array<ImageView*, NUM_RT> color_buffers{color_buffer};
- CreateFramebuffer(runtime, color_buffers, depth_buffer);
+ CreateFramebuffer(runtime, color_buffers, depth_buffer, is_rescaled);
}
Framebuffer::~Framebuffer() = default;
void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
std::span<ImageView*, NUM_RT> color_buffers,
- ImageView* depth_buffer) {
+ ImageView* depth_buffer, bool is_rescaled) {
std::vector<VkImageView> attachments;
RenderPassKey renderpass_key{};
s32 num_layers = 1;
+ const auto& resolution = runtime.resolution;
+
+ u32 width = 0;
+ u32 height = 0;
for (size_t index = 0; index < NUM_RT; ++index) {
const ImageView* const color_buffer = color_buffers[index];
if (!color_buffer) {
renderpass_key.color_formats[index] = PixelFormat::Invalid;
continue;
}
+ width = std::max(width, is_rescaled ? resolution.ScaleUp(color_buffer->size.width)
+ : color_buffer->size.width);
+ height = std::max(height, is_rescaled ? resolution.ScaleUp(color_buffer->size.height)
+ : color_buffer->size.height);
attachments.push_back(color_buffer->RenderTarget());
renderpass_key.color_formats[index] = color_buffer->format;
num_layers = std::max(num_layers, color_buffer->range.extent.layers);
@@ -1743,6 +1804,10 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
}
const size_t num_colors = attachments.size();
if (depth_buffer) {
+ width = std::max(width, is_rescaled ? resolution.ScaleUp(depth_buffer->size.width)
+ : depth_buffer->size.width);
+ height = std::max(height, is_rescaled ? resolution.ScaleUp(depth_buffer->size.height)
+ : depth_buffer->size.height);
attachments.push_back(depth_buffer->RenderTarget());
renderpass_key.depth_format = depth_buffer->format;
num_layers = std::max(num_layers, depth_buffer->range.extent.layers);
@@ -1759,6 +1824,8 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
renderpass_key.samples = samples;
renderpass = runtime.render_pass_cache.Get(renderpass_key);
+ render_area.width = std::min(render_area.width, width);
+ render_area.height = std::min(render_area.height, height);
num_color_buffers = static_cast<u32>(num_colors);
framebuffer = runtime.device.GetLogical().CreateFramebuffer({
@@ -1780,7 +1847,7 @@ void TextureCacheRuntime::AccelerateImageUpload(
if (IsPixelFormatASTC(image.info.format)) {
return astc_decoder_pass.Assemble(image, map, swizzles);
}
- UNREACHABLE();
+ ASSERT(false);
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index c81130dd2..0b7ac0df1 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -34,11 +33,11 @@ class ImageView;
class Framebuffer;
class RenderPassCache;
class StagingBufferPool;
-class VKScheduler;
+class Scheduler;
class TextureCacheRuntime {
public:
- explicit TextureCacheRuntime(const Device& device_, VKScheduler& scheduler_,
+ explicit TextureCacheRuntime(const Device& device_, Scheduler& scheduler_,
MemoryAllocator& memory_allocator_,
StagingBufferPool& staging_buffer_pool_,
BlitImageHelper& blit_image_helper_,
@@ -55,6 +54,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,
@@ -90,7 +93,7 @@ public:
[[nodiscard]] VkBuffer GetTemporaryBuffer(size_t needed_size);
const Device& device;
- VKScheduler& scheduler;
+ Scheduler& scheduler;
MemoryAllocator& memory_allocator;
StagingBufferPool& staging_buffer_pool;
BlitImageHelper& blit_image_helper;
@@ -151,7 +154,7 @@ private:
bool NeedsScaleHelper() const;
- VKScheduler* scheduler{};
+ Scheduler* scheduler{};
TextureCacheRuntime* runtime{};
vk::Image original_image;
@@ -265,7 +268,7 @@ public:
ImageView* depth_buffer, const VideoCommon::RenderTargets& key);
explicit Framebuffer(TextureCacheRuntime& runtime, ImageView* color_buffer,
- ImageView* depth_buffer, VkExtent2D extent);
+ ImageView* depth_buffer, VkExtent2D extent, bool is_rescaled);
~Framebuffer();
@@ -276,7 +279,8 @@ public:
Framebuffer& operator=(Framebuffer&&) = default;
void CreateFramebuffer(TextureCacheRuntime& runtime,
- std::span<ImageView*, NUM_RT> color_buffers, ImageView* depth_buffer);
+ std::span<ImageView*, NUM_RT> color_buffers, ImageView* depth_buffer,
+ bool is_rescaled = false);
[[nodiscard]] VkFramebuffer Handle() const noexcept {
return *framebuffer;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache_base.cpp b/src/video_core/renderer_vulkan/vk_texture_cache_base.cpp
index 44e688342..7e1c3a863 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache_base.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache_base.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/texture_cache/texture_cache.h"
diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
index 0df3a7fe9..4d4a6753b 100644
--- a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
+++ b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
@@ -1,11 +1,9 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <variant>
#include <boost/container/static_vector.hpp>
-#include "common/assert.h"
#include "common/logging/log.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
@@ -14,18 +12,18 @@
namespace Vulkan {
-VKUpdateDescriptorQueue::VKUpdateDescriptorQueue(const Device& device_, VKScheduler& scheduler_)
+UpdateDescriptorQueue::UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_)
: device{device_}, scheduler{scheduler_} {
payload_cursor = payload.data();
}
-VKUpdateDescriptorQueue::~VKUpdateDescriptorQueue() = default;
+UpdateDescriptorQueue::~UpdateDescriptorQueue() = default;
-void VKUpdateDescriptorQueue::TickFrame() {
+void UpdateDescriptorQueue::TickFrame() {
payload_cursor = payload.data();
}
-void VKUpdateDescriptorQueue::Acquire() {
+void UpdateDescriptorQueue::Acquire() {
// Minimum number of entries required.
// This is the maximum number of entries a single draw call migth use.
static constexpr size_t MIN_ENTRIES = 0x400;
diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.h b/src/video_core/renderer_vulkan/vk_update_descriptor.h
index d7de4c490..625bcc809 100644
--- a/src/video_core/renderer_vulkan/vk_update_descriptor.h
+++ b/src/video_core/renderer_vulkan/vk_update_descriptor.h
@@ -1,18 +1,16 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
-#include "common/common_types.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
class Device;
-class VKScheduler;
+class Scheduler;
struct DescriptorUpdateEntry {
struct Empty {};
@@ -30,10 +28,10 @@ struct DescriptorUpdateEntry {
};
};
-class VKUpdateDescriptorQueue final {
+class UpdateDescriptorQueue final {
public:
- explicit VKUpdateDescriptorQueue(const Device& device_, VKScheduler& scheduler_);
- ~VKUpdateDescriptorQueue();
+ explicit UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_);
+ ~UpdateDescriptorQueue();
void TickFrame();
@@ -73,7 +71,7 @@ public:
private:
const Device& device;
- VKScheduler& scheduler;
+ Scheduler& scheduler;
DescriptorUpdateEntry* payload_cursor = nullptr;
const DescriptorUpdateEntry* upload_start = nullptr;
diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp
index 87636857d..f53066579 100644
--- a/src/video_core/shader_cache.cpp
+++ b/src/video_core/shader_cache.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
@@ -9,6 +8,7 @@
#include "common/assert.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"
#include "shader_recompiler/object_pool.h"
+#include "video_core/control/channel_state.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
@@ -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);
}
@@ -34,29 +34,25 @@ void ShaderCache::SyncGuestHost() {
RemovePendingShaders();
}
-ShaderCache::ShaderCache(VideoCore::RasterizerInterface& rasterizer_,
- Tegra::MemoryManager& gpu_memory_, Tegra::Engines::Maxwell3D& maxwell3d_,
- Tegra::Engines::KeplerCompute& kepler_compute_)
- : gpu_memory{gpu_memory_}, maxwell3d{maxwell3d_}, kepler_compute{kepler_compute_},
- rasterizer{rasterizer_} {}
+ShaderCache::ShaderCache(VideoCore::RasterizerInterface& rasterizer_) : rasterizer{rasterizer_} {}
bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) {
- auto& dirty{maxwell3d.dirty.flags};
+ auto& dirty{maxwell3d->dirty.flags};
if (!dirty[VideoCommon::Dirty::Shaders]) {
return last_shaders_valid;
}
dirty[VideoCommon::Dirty::Shaders] = false;
- const GPUVAddr base_addr{maxwell3d.regs.code_address.CodeAddress()};
+ const GPUVAddr base_addr{maxwell3d->regs.code_address.CodeAddress()};
for (size_t index = 0; index < Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram; ++index) {
- if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
+ if (!maxwell3d->regs.IsShaderConfigEnabled(index)) {
unique_hashes[index] = 0;
continue;
}
- const auto& shader_config{maxwell3d.regs.shader_config[index]};
+ const auto& shader_config{maxwell3d->regs.shader_config[index]};
const auto program{static_cast<Tegra::Engines::Maxwell3D::Regs::ShaderProgram>(index)};
const GPUVAddr shader_addr{base_addr + shader_config.offset};
- const std::optional<VAddr> cpu_shader_addr{gpu_memory.GpuToCpuAddress(shader_addr)};
+ const std::optional<VAddr> cpu_shader_addr{gpu_memory->GpuToCpuAddress(shader_addr)};
if (!cpu_shader_addr) {
LOG_ERROR(HW_GPU, "Invalid GPU address for shader 0x{:016x}", shader_addr);
last_shaders_valid = false;
@@ -65,7 +61,7 @@ bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) {
const ShaderInfo* shader_info{TryGet(*cpu_shader_addr)};
if (!shader_info) {
const u32 start_address{shader_config.offset};
- GraphicsEnvironment env{maxwell3d, gpu_memory, program, base_addr, start_address};
+ GraphicsEnvironment env{*maxwell3d, *gpu_memory, program, base_addr, start_address};
shader_info = MakeShaderInfo(env, *cpu_shader_addr);
}
shader_infos[index] = shader_info;
@@ -76,10 +72,10 @@ bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) {
}
const ShaderInfo* ShaderCache::ComputeShader() {
- const GPUVAddr program_base{kepler_compute.regs.code_loc.Address()};
- const auto& qmd{kepler_compute.launch_description};
+ const GPUVAddr program_base{kepler_compute->regs.code_loc.Address()};
+ const auto& qmd{kepler_compute->launch_description};
const GPUVAddr shader_addr{program_base + qmd.program_start};
- const std::optional<VAddr> cpu_shader_addr{gpu_memory.GpuToCpuAddress(shader_addr)};
+ const std::optional<VAddr> cpu_shader_addr{gpu_memory->GpuToCpuAddress(shader_addr)};
if (!cpu_shader_addr) {
LOG_ERROR(HW_GPU, "Invalid GPU address for shader 0x{:016x}", shader_addr);
return nullptr;
@@ -87,22 +83,22 @@ const ShaderInfo* ShaderCache::ComputeShader() {
if (const ShaderInfo* const shader = TryGet(*cpu_shader_addr)) {
return shader;
}
- ComputeEnvironment env{kepler_compute, gpu_memory, program_base, qmd.program_start};
+ ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start};
return MakeShaderInfo(env, *cpu_shader_addr);
}
void ShaderCache::GetGraphicsEnvironments(GraphicsEnvironments& result,
const std::array<u64, NUM_PROGRAMS>& unique_hashes) {
size_t env_index{};
- const GPUVAddr base_addr{maxwell3d.regs.code_address.CodeAddress()};
+ const GPUVAddr base_addr{maxwell3d->regs.code_address.CodeAddress()};
for (size_t index = 0; index < NUM_PROGRAMS; ++index) {
if (unique_hashes[index] == 0) {
continue;
}
const auto program{static_cast<Tegra::Engines::Maxwell3D::Regs::ShaderProgram>(index)};
auto& env{result.envs[index]};
- const u32 start_address{maxwell3d.regs.shader_config[index].offset};
- env = GraphicsEnvironment{maxwell3d, gpu_memory, program, base_addr, start_address};
+ const u32 start_address{maxwell3d->regs.shader_config[index].offset};
+ env = GraphicsEnvironment{*maxwell3d, *gpu_memory, program, base_addr, start_address};
env.SetCachedSize(shader_infos[index]->size_bytes);
result.env_ptrs[env_index++] = &env;
}
@@ -124,8 +120,8 @@ void ShaderCache::Register(std::unique_ptr<ShaderInfo> data, VAddr addr, size_t
const VAddr addr_end = addr + size;
Entry* const entry = NewEntry(addr, addr_end, data.get());
- const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
- for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
+ const u64 page_end = (addr_end + YUZU_PAGESIZE - 1) >> YUZU_PAGEBITS;
+ for (u64 page = addr >> YUZU_PAGEBITS; page < page_end; ++page) {
invalidation_cache[page].push_back(entry);
}
@@ -136,8 +132,8 @@ void ShaderCache::Register(std::unique_ptr<ShaderInfo> data, VAddr addr, size_t
void ShaderCache::InvalidatePagesInRegion(VAddr addr, size_t size) {
const VAddr addr_end = addr + size;
- const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
- for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
+ const u64 page_end = (addr_end + YUZU_PAGESIZE - 1) >> YUZU_PAGEBITS;
+ for (u64 page = addr >> YUZU_PAGEBITS; page < page_end; ++page) {
auto it = invalidation_cache.find(page);
if (it == invalidation_cache.end()) {
continue;
@@ -190,8 +186,8 @@ void ShaderCache::InvalidatePageEntries(std::vector<Entry*>& entries, VAddr addr
}
void ShaderCache::RemoveEntryFromInvalidationCache(const Entry* entry) {
- const u64 page_end = (entry->addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
- for (u64 page = entry->addr_start >> PAGE_BITS; page < page_end; ++page) {
+ const u64 page_end = (entry->addr_end + YUZU_PAGESIZE - 1) >> YUZU_PAGEBITS;
+ for (u64 page = entry->addr_start >> YUZU_PAGEBITS; page < page_end; ++page) {
const auto entries_it = invalidation_cache.find(page);
ASSERT(entries_it != invalidation_cache.end());
std::vector<Entry*>& entries = entries_it->second;
diff --git a/src/video_core/shader_cache.h b/src/video_core/shader_cache.h
index 8836bc8c6..a4391202d 100644
--- a/src/video_core/shader_cache.h
+++ b/src/video_core/shader_cache.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -13,6 +12,7 @@
#include <vector>
#include "common/common_types.h"
+#include "video_core/control/channel_state_cache.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/shader_environment.h"
@@ -20,6 +20,10 @@ namespace Tegra {
class MemoryManager;
}
+namespace Tegra::Control {
+struct ChannelState;
+}
+
namespace VideoCommon {
class GenericEnvironment;
@@ -29,9 +33,9 @@ struct ShaderInfo {
size_t size_bytes{};
};
-class ShaderCache {
- static constexpr u64 PAGE_BITS = 14;
- static constexpr u64 PAGE_SIZE = u64(1) << PAGE_BITS;
+class ShaderCache : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
+ static constexpr u64 YUZU_PAGEBITS = 14;
+ static constexpr u64 YUZU_PAGESIZE = u64(1) << YUZU_PAGEBITS;
static constexpr size_t NUM_PROGRAMS = 6;
@@ -72,9 +76,7 @@ protected:
}
};
- explicit ShaderCache(VideoCore::RasterizerInterface& rasterizer_,
- Tegra::MemoryManager& gpu_memory_, Tegra::Engines::Maxwell3D& maxwell3d_,
- Tegra::Engines::KeplerCompute& kepler_compute_);
+ explicit ShaderCache(VideoCore::RasterizerInterface& rasterizer_);
/// @brief Update the hashes and information of shader stages
/// @param unique_hashes Shader hashes to store into when a stage is enabled
@@ -89,10 +91,6 @@ protected:
void GetGraphicsEnvironments(GraphicsEnvironments& result,
const std::array<u64, NUM_PROGRAMS>& unique_hashes);
- Tegra::MemoryManager& gpu_memory;
- Tegra::Engines::Maxwell3D& maxwell3d;
- Tegra::Engines::KeplerCompute& kepler_compute;
-
std::array<const ShaderInfo*, NUM_PROGRAMS> shader_infos{};
bool last_shaders_valid = false;
diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp
index 3e673c437..5f7625947 100644
--- a/src/video_core/shader_environment.cpp
+++ b/src/video_core/shader_environment.cpp
@@ -1,9 +1,7 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
-#include <bit>
#include <filesystem>
#include <fstream>
#include <memory>
@@ -41,7 +39,8 @@ static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) {
return Shader::TextureType::Color1D;
case Tegra::Texture::TextureType::Texture2D:
case Tegra::Texture::TextureType::Texture2DNoMipmap:
- return Shader::TextureType::Color2D;
+ return entry.normalized_coords ? Shader::TextureType::Color2D
+ : Shader::TextureType::Color2DRect;
case Tegra::Texture::TextureType::Texture3D:
return Shader::TextureType::Color3D;
case Tegra::Texture::TextureType::TextureCubemap:
@@ -55,7 +54,8 @@ static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) {
case Tegra::Texture::TextureType::TextureCubeArray:
return Shader::TextureType::ColorArrayCube;
default:
- throw Shader::NotImplementedException("Unknown texture type");
+ UNIMPLEMENTED();
+ return Shader::TextureType::Color2D;
}
}
@@ -190,11 +190,11 @@ void GenericEnvironment::Serialize(std::ofstream& file) const {
.write(reinterpret_cast<const char*>(&cached_highest), sizeof(cached_highest))
.write(reinterpret_cast<const char*>(&stage), sizeof(stage))
.write(reinterpret_cast<const char*>(code.data()), code_size);
- for (const auto [key, type] : texture_types) {
+ for (const auto& [key, type] : texture_types) {
file.write(reinterpret_cast<const char*>(&key), sizeof(key))
.write(reinterpret_cast<const char*>(&type), sizeof(type));
}
- for (const auto [key, type] : cbuf_values) {
+ for (const auto& [key, type] : cbuf_values) {
file.write(reinterpret_cast<const char*>(&key), sizeof(key))
.write(reinterpret_cast<const char*>(&type), sizeof(type));
}
@@ -282,7 +282,7 @@ GraphicsEnvironment::GraphicsEnvironment(Tegra::Engines::Maxwell3D& maxwell3d_,
stage_index = 4;
break;
default:
- UNREACHABLE_MSG("Invalid program={}", program);
+ ASSERT_MSG(false, "Invalid program={}", program);
break;
}
const u64 local_size{sph.LocalMemorySize()};
diff --git a/src/video_core/shader_environment.h b/src/video_core/shader_environment.h
index aae762b27..5a145f33a 100644
--- a/src/video_core/shader_environment.h
+++ b/src/video_core/shader_environment.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/shader_notify.cpp b/src/video_core/shader_notify.cpp
index bcaf5f575..631891ec2 100644
--- a/src/video_core/shader_notify.cpp
+++ b/src/video_core/shader_notify.cpp
@@ -1,10 +1,8 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
#include <chrono>
-#include <optional>
#include "video_core/shader_notify.h"
diff --git a/src/video_core/shader_notify.h b/src/video_core/shader_notify.h
index 4d8d52071..696a51384 100644
--- a/src/video_core/shader_notify.h
+++ b/src/video_core/shader_notify.h
@@ -1,12 +1,10 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <chrono>
-#include <optional>
namespace VideoCore {
class ShaderNotify {
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index a36015c8c..a2bf08294 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
#include "common/math_util.h"
@@ -29,7 +28,7 @@ SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_t
return SurfaceTarget::Texture2DArray;
default:
LOG_CRITICAL(HW_GPU, "Unimplemented texture_type={}", texture_type);
- UNREACHABLE();
+ ASSERT(false);
return SurfaceTarget::Texture2D;
}
}
@@ -48,7 +47,7 @@ bool SurfaceTargetIsLayered(SurfaceTarget target) {
return true;
default:
LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", target);
- UNREACHABLE();
+ ASSERT(false);
return false;
}
}
@@ -67,7 +66,7 @@ bool SurfaceTargetIsArray(SurfaceTarget target) {
return true;
default:
LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", target);
- UNREACHABLE();
+ ASSERT(false);
return false;
}
}
@@ -190,13 +189,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);
@@ -247,6 +246,9 @@ bool IsPixelFormatASTC(PixelFormat format) {
case PixelFormat::ASTC_2D_10X8_SRGB:
case PixelFormat::ASTC_2D_6X6_UNORM:
case PixelFormat::ASTC_2D_6X6_SRGB:
+ case PixelFormat::ASTC_2D_10X6_UNORM:
+ case PixelFormat::ASTC_2D_10X5_UNORM:
+ case PixelFormat::ASTC_2D_10X5_SRGB:
case PixelFormat::ASTC_2D_10X10_UNORM:
case PixelFormat::ASTC_2D_10X10_SRGB:
case PixelFormat::ASTC_2D_12X12_UNORM:
@@ -276,6 +278,7 @@ bool IsPixelFormatSRGB(PixelFormat format) {
case PixelFormat::ASTC_2D_5X5_SRGB:
case PixelFormat::ASTC_2D_10X8_SRGB:
case PixelFormat::ASTC_2D_6X6_SRGB:
+ case PixelFormat::ASTC_2D_10X5_SRGB:
case PixelFormat::ASTC_2D_10X10_SRGB:
case PixelFormat::ASTC_2D_12X12_SRGB:
case PixelFormat::ASTC_2D_8X6_SRGB:
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index 33e8d24ab..57ca7f597 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -25,6 +24,7 @@ enum class PixelFormat {
A2B10G10R10_UNORM,
A2B10G10R10_UINT,
A1B5G5R5_UNORM,
+ A5B5G5R1_UNORM,
R8_UNORM,
R8_SNORM,
R8_SINT,
@@ -82,6 +82,7 @@ enum class PixelFormat {
BC3_SRGB,
BC7_SRGB,
A4B4G4R4_UNORM,
+ G4R4_UNORM,
ASTC_2D_4X4_SRGB,
ASTC_2D_8X8_SRGB,
ASTC_2D_8X5_SRGB,
@@ -92,6 +93,9 @@ enum class PixelFormat {
ASTC_2D_10X8_SRGB,
ASTC_2D_6X6_UNORM,
ASTC_2D_6X6_SRGB,
+ ASTC_2D_10X6_UNORM,
+ ASTC_2D_10X5_UNORM,
+ ASTC_2D_10X5_SRGB,
ASTC_2D_10X10_UNORM,
ASTC_2D_10X10_SRGB,
ASTC_2D_12X12_UNORM,
@@ -145,7 +149,7 @@ enum class SurfaceTarget {
TextureCubeArray,
};
-constexpr std::array<u32, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
+constexpr std::array<u8, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
1, // A8B8G8R8_UNORM
1, // A8B8G8R8_SNORM
1, // A8B8G8R8_SINT
@@ -156,6 +160,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 +218,7 @@ constexpr std::array<u32, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
4, // BC3_SRGB
4, // BC7_SRGB
1, // A4B4G4R4_UNORM
+ 1, // G4R4_UNORM
4, // ASTC_2D_4X4_SRGB
8, // ASTC_2D_8X8_SRGB
8, // ASTC_2D_8X5_SRGB
@@ -223,6 +229,9 @@ constexpr std::array<u32, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
10, // ASTC_2D_10X8_SRGB
6, // ASTC_2D_6X6_UNORM
6, // ASTC_2D_6X6_SRGB
+ 10, // ASTC_2D_10X6_UNORM
+ 10, // ASTC_2D_10X5_UNORM
+ 10, // ASTC_2D_10X5_SRGB
10, // ASTC_2D_10X10_UNORM
10, // ASTC_2D_10X10_SRGB
12, // ASTC_2D_12X12_UNORM
@@ -245,7 +254,7 @@ constexpr u32 DefaultBlockWidth(PixelFormat format) {
return BLOCK_WIDTH_TABLE[static_cast<std::size_t>(format)];
}
-constexpr std::array<u32, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
+constexpr std::array<u8, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
1, // A8B8G8R8_UNORM
1, // A8B8G8R8_SNORM
1, // A8B8G8R8_SINT
@@ -256,6 +265,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 +323,7 @@ constexpr std::array<u32, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
4, // BC3_SRGB
4, // BC7_SRGB
1, // A4B4G4R4_UNORM
+ 1, // G4R4_UNORM
4, // ASTC_2D_4X4_SRGB
8, // ASTC_2D_8X8_SRGB
5, // ASTC_2D_8X5_SRGB
@@ -323,6 +334,9 @@ constexpr std::array<u32, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
8, // ASTC_2D_10X8_SRGB
6, // ASTC_2D_6X6_UNORM
6, // ASTC_2D_6X6_SRGB
+ 6, // ASTC_2D_10X6_UNORM
+ 5, // ASTC_2D_10X5_UNORM
+ 5, // ASTC_2D_10X5_SRGB
10, // ASTC_2D_10X10_UNORM
10, // ASTC_2D_10X10_SRGB
12, // ASTC_2D_12X12_UNORM
@@ -345,7 +359,7 @@ constexpr u32 DefaultBlockHeight(PixelFormat format) {
return BLOCK_HEIGHT_TABLE[static_cast<std::size_t>(format)];
}
-constexpr std::array<u32, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
+constexpr std::array<u8, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
32, // A8B8G8R8_UNORM
32, // A8B8G8R8_SNORM
32, // A8B8G8R8_SINT
@@ -356,6 +370,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 +428,7 @@ constexpr std::array<u32, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
128, // BC3_SRGB
128, // BC7_UNORM
16, // A4B4G4R4_UNORM
+ 8, // G4R4_UNORM
128, // ASTC_2D_4X4_SRGB
128, // ASTC_2D_8X8_SRGB
128, // ASTC_2D_8X5_SRGB
@@ -423,6 +439,9 @@ constexpr std::array<u32, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
128, // ASTC_2D_10X8_SRGB
128, // ASTC_2D_6X6_UNORM
128, // ASTC_2D_6X6_SRGB
+ 128, // ASTC_2D_10X6_UNORM
+ 128, // ASTC_2D_10X5_UNORM
+ 128, // ASTC_2D_10X5_SRGB
128, // ASTC_2D_10X10_UNORM
128, // ASTC_2D_10X10_SRGB
128, // ASTC_2D_12X12_UNORM
@@ -460,7 +479,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/accelerated_swizzle.cpp b/src/video_core/texture_cache/accelerated_swizzle.cpp
index 15585caeb..70be1657e 100644
--- a/src/video_core/texture_cache/accelerated_swizzle.cpp
+++ b/src/video_core/texture_cache/accelerated_swizzle.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <bit>
diff --git a/src/video_core/texture_cache/accelerated_swizzle.h b/src/video_core/texture_cache/accelerated_swizzle.h
index a11c924e1..d4250da68 100644
--- a/src/video_core/texture_cache/accelerated_swizzle.h
+++ b/src/video_core/texture_cache/accelerated_swizzle.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/texture_cache/decode_bc4.cpp b/src/video_core/texture_cache/decode_bc4.cpp
index 017327975..ef98afdca 100644
--- a/src/video_core/texture_cache/decode_bc4.cpp
+++ b/src/video_core/texture_cache/decode_bc4.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
diff --git a/src/video_core/texture_cache/decode_bc4.h b/src/video_core/texture_cache/decode_bc4.h
index 63fb23508..ab2f735be 100644
--- a/src/video_core/texture_cache/decode_bc4.h
+++ b/src/video_core/texture_cache/decode_bc4.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/texture_cache/descriptor_table.h b/src/video_core/texture_cache/descriptor_table.h
index 3a03b786f..b18e3838f 100644
--- a/src/video_core/texture_cache/descriptor_table.h
+++ b/src/video_core/texture_cache/descriptor_table.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -9,7 +8,6 @@
#include "common/common_types.h"
#include "common/div_ceil.h"
-#include "common/logging/log.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp
index afa807d5d..ad935d386 100644
--- a/src/video_core/texture_cache/format_lookup_table.cpp
+++ b/src/video_core/texture_cache/format_lookup_table.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
#include "common/logging/log.h"
@@ -63,6 +62,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::G4R4_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 +146,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):
@@ -201,6 +206,12 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
return PixelFormat::ASTC_2D_6X6_UNORM;
case Hash(TextureFormat::ASTC_2D_6X6, UNORM, SRGB):
return PixelFormat::ASTC_2D_6X6_SRGB;
+ case Hash(TextureFormat::ASTC_2D_10X6, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_10X6_UNORM;
+ case Hash(TextureFormat::ASTC_2D_10X5, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_10X5_UNORM;
+ case Hash(TextureFormat::ASTC_2D_10X5, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_10X5_SRGB;
case Hash(TextureFormat::ASTC_2D_10X10, UNORM, LINEAR):
return PixelFormat::ASTC_2D_10X10_UNORM;
case Hash(TextureFormat::ASTC_2D_10X10, UNORM, SRGB):
diff --git a/src/video_core/texture_cache/format_lookup_table.h b/src/video_core/texture_cache/format_lookup_table.h
index 729533999..f504a6cd0 100644
--- a/src/video_core/texture_cache/format_lookup_table.h
+++ b/src/video_core/texture_cache/format_lookup_table.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/texture_cache/formatter.cpp b/src/video_core/texture_cache/formatter.cpp
index 249cc4d0f..ee4f2d406 100644
--- a/src/video_core/texture_cache/formatter.cpp
+++ b/src/video_core/texture_cache/formatter.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <string>
diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h
index b2c81057b..acc854715 100644
--- a/src/video_core/texture_cache/formatter.h
+++ b/src/video_core/texture_cache/formatter.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -38,6 +37,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 +153,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
return "BC7_SRGB";
case PixelFormat::A4B4G4R4_UNORM:
return "A4B4G4R4_UNORM";
+ case PixelFormat::G4R4_UNORM:
+ return "G4R4_UNORM";
case PixelFormat::ASTC_2D_4X4_SRGB:
return "ASTC_2D_4X4_SRGB";
case PixelFormat::ASTC_2D_8X8_SRGB:
@@ -172,6 +175,12 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
return "ASTC_2D_6X6_UNORM";
case PixelFormat::ASTC_2D_6X6_SRGB:
return "ASTC_2D_6X6_SRGB";
+ case PixelFormat::ASTC_2D_10X6_UNORM:
+ return "ASTC_2D_10X6_UNORM";
+ case PixelFormat::ASTC_2D_10X5_UNORM:
+ return "ASTC_2D_10X5_UNORM";
+ case PixelFormat::ASTC_2D_10X5_SRGB:
+ return "ASTC_2D_10X5_SRGB";
case PixelFormat::ASTC_2D_10X10_UNORM:
return "ASTC_2D_10X10_UNORM";
case PixelFormat::ASTC_2D_10X10_SRGB:
diff --git a/src/video_core/texture_cache/image_base.cpp b/src/video_core/texture_cache/image_base.cpp
index 3db2fdf34..91512022f 100644
--- a/src/video_core/texture_cache/image_base.cpp
+++ b/src/video_core/texture_cache/image_base.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <optional>
@@ -8,6 +7,7 @@
#include <vector>
#include "common/common_types.h"
+#include "common/div_ceil.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/formatter.h"
#include "video_core/texture_cache/image_base.h"
@@ -183,10 +183,6 @@ void AddImageAlias(ImageBase& lhs, ImageBase& rhs, ImageId lhs_id, ImageId rhs_i
};
const bool is_lhs_compressed = lhs_block.width > 1 || lhs_block.height > 1;
const bool is_rhs_compressed = rhs_block.width > 1 || rhs_block.height > 1;
- if (is_lhs_compressed && is_rhs_compressed) {
- LOG_ERROR(HW_GPU, "Compressed to compressed image aliasing is not implemented");
- return;
- }
const s32 lhs_mips = lhs.info.resources.levels;
const s32 rhs_mips = rhs.info.resources.levels;
const s32 num_mips = std::min(lhs_mips - base->level, rhs_mips);
@@ -200,12 +196,12 @@ void AddImageAlias(ImageBase& lhs, ImageBase& rhs, ImageId lhs_id, ImageId rhs_i
Extent3D lhs_size = MipSize(lhs.info.size, base->level + mip_level);
Extent3D rhs_size = MipSize(rhs.info.size, mip_level);
if (is_lhs_compressed) {
- lhs_size.width /= lhs_block.width;
- lhs_size.height /= lhs_block.height;
+ lhs_size.width = Common::DivCeil(lhs_size.width, lhs_block.width);
+ lhs_size.height = Common::DivCeil(lhs_size.height, lhs_block.height);
}
if (is_rhs_compressed) {
- rhs_size.width /= rhs_block.width;
- rhs_size.height /= rhs_block.height;
+ rhs_size.width = Common::DivCeil(rhs_size.width, rhs_block.width);
+ rhs_size.height = Common::DivCeil(rhs_size.height, rhs_block.height);
}
const Extent3D copy_size{
.width = std::min(lhs_size.width, rhs_size.width),
diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h
index 89c111c00..620565684 100644
--- a/src/video_core/texture_cache/image_base.h
+++ b/src/video_core/texture_cache/image_base.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -33,11 +32,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)
@@ -88,6 +88,9 @@ struct ImageBase {
u32 scale_rating = 0;
u64 scale_tick = 0;
bool has_scaled = false;
+
+ size_t channel = 0;
+
ImageFlagBits flags = ImageFlagBits::CpuModified;
GPUVAddr gpu_addr = 0;
diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp
index afb94082b..6c073ee57 100644
--- a/src/video_core/texture_cache/image_info.cpp
+++ b/src/video_core/texture_cache/image_info.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "video_core/surface.h"
@@ -95,7 +94,7 @@ ImageInfo::ImageInfo(const TICEntry& config) noexcept {
resources.layers = 1;
break;
default:
- UNREACHABLE_MSG("Invalid texture_type={}", static_cast<int>(config.texture_type.Value()));
+ ASSERT_MSG(false, "Invalid texture_type={}", static_cast<int>(config.texture_type.Value()));
break;
}
if (type != ImageType::Linear) {
diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h
index 5932dcaba..93755e15e 100644
--- a/src/video_core/texture_cache/image_info.h
+++ b/src/video_core/texture_cache/image_info.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp
index c7b4fc231..04fb84bfa 100644
--- a/src/video_core/texture_cache/image_view_base.cpp
+++ b/src/video_core/texture_cache/image_view_base.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
diff --git a/src/video_core/texture_cache/image_view_base.h b/src/video_core/texture_cache/image_view_base.h
index 9c24c5359..69c9776e7 100644
--- a/src/video_core/texture_cache/image_view_base.h
+++ b/src/video_core/texture_cache/image_view_base.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/texture_cache/image_view_info.cpp b/src/video_core/texture_cache/image_view_info.cpp
index e751f26c7..f47885147 100644
--- a/src/video_core/texture_cache/image_view_info.cpp
+++ b/src/video_core/texture_cache/image_view_info.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <limits>
@@ -72,7 +71,7 @@ ImageViewInfo::ImageViewInfo(const TICEntry& config, s32 base_layer) noexcept
range.extent.layers = config.Depth() * 6;
break;
default:
- UNREACHABLE_MSG("Invalid texture_type={}", static_cast<int>(config.texture_type.Value()));
+ ASSERT_MSG(false, "Invalid texture_type={}", static_cast<int>(config.texture_type.Value()));
break;
}
}
diff --git a/src/video_core/texture_cache/image_view_info.h b/src/video_core/texture_cache/image_view_info.h
index 0c1f99117..921f88988 100644
--- a/src/video_core/texture_cache/image_view_info.h
+++ b/src/video_core/texture_cache/image_view_info.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/texture_cache/render_targets.h b/src/video_core/texture_cache/render_targets.h
index 0cb227d69..1efbd6507 100644
--- a/src/video_core/texture_cache/render_targets.h
+++ b/src/video_core/texture_cache/render_targets.h
@@ -1,12 +1,10 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <span>
-#include <utility>
#include "common/bit_cast.h"
#include "video_core/texture_cache/types.h"
@@ -28,6 +26,7 @@ struct RenderTargets {
ImageViewId depth_buffer_id{};
std::array<u8, NUM_RT> draw_buffers{};
Extent2D size{};
+ bool is_rescaled{};
};
} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/samples_helper.h b/src/video_core/texture_cache/samples_helper.h
index 04539a43c..d552bccf0 100644
--- a/src/video_core/texture_cache/samples_helper.h
+++ b/src/video_core/texture_cache/samples_helper.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -24,7 +23,7 @@ namespace VideoCommon {
case 16:
return {2, 2};
}
- UNREACHABLE_MSG("Invalid number of samples={}", num_samples);
+ ASSERT_MSG(false, "Invalid number of samples={}", num_samples);
return {1, 1};
}
@@ -48,7 +47,7 @@ namespace VideoCommon {
case MsaaMode::Msaa4x4:
return 16;
}
- UNREACHABLE_MSG("Invalid MSAA mode={}", static_cast<int>(msaa_mode));
+ ASSERT_MSG(false, "Invalid MSAA mode={}", static_cast<int>(msaa_mode));
return 1;
}
diff --git a/src/video_core/texture_cache/slot_vector.h b/src/video_core/texture_cache/slot_vector.h
index 50df06409..46e8a86e6 100644
--- a/src/video_core/texture_cache/slot_vector.h
+++ b/src/video_core/texture_cache/slot_vector.h
@@ -1,13 +1,10 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
-#include <array>
#include <bit>
-#include <concepts>
#include <numeric>
#include <type_traits>
#include <utility>
diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp
new file mode 100644
index 000000000..8a9a32f44
--- /dev/null
+++ b/src/video_core/texture_cache/texture_cache.cpp
@@ -0,0 +1,15 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "video_core/control/channel_state_cache.inc"
+#include "video_core/texture_cache/texture_cache_base.h"
+
+namespace VideoCommon {
+
+TextureCacheChannelInfo::TextureCacheChannelInfo(Tegra::Control::ChannelState& state) noexcept
+ : ChannelInfo(state), graphics_image_table{gpu_memory}, graphics_sampler_table{gpu_memory},
+ compute_image_table{gpu_memory}, compute_sampler_table{gpu_memory} {}
+
+template class VideoCommon::ChannelSetupCaches<VideoCommon::TextureCacheChannelInfo>;
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 198bb0cfb..eaf4a1c95 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -8,6 +7,7 @@
#include "common/alignment.h"
#include "common/settings.h"
+#include "video_core/control/channel_state.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/texture_cache/image_view_base.h"
@@ -30,12 +30,8 @@ using VideoCore::Surface::SurfaceType;
using namespace Common::Literals;
template <class P>
-TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface& rasterizer_,
- Tegra::Engines::Maxwell3D& maxwell3d_,
- Tegra::Engines::KeplerCompute& kepler_compute_,
- Tegra::MemoryManager& gpu_memory_)
- : runtime{runtime_}, rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
- kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_} {
+TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface& rasterizer_)
+ : runtime{runtime_}, rasterizer{rasterizer_} {
// Configure null sampler
TSCEntry sampler_descriptor{};
sampler_descriptor.min_filter.Assign(Tegra::Texture::TextureFilter::Linear);
@@ -50,14 +46,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 +68,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) {
@@ -85,13 +90,25 @@ void TextureCache<P>::RunGarbageCollector() {
const auto copies = FullDownloadCopies(image.info);
image.DownloadMemory(map, copies);
runtime.Finish();
- SwizzleImage(gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span);
+ SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span);
}
if (True(image.flags & ImageFlagBits::Tracked)) {
UntrackImage(image, image_id);
}
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 +116,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 +127,7 @@ void TextureCache<P>::TickFrame() {
sentenced_framebuffers.Tick();
sentenced_image_view.Tick();
runtime.TickFrame();
+ critical_gc = 0;
++frame_tick;
}
@@ -127,22 +149,24 @@ void TextureCache<P>::MarkModification(ImageId id) noexcept {
template <class P>
template <bool has_blacklists>
void TextureCache<P>::FillGraphicsImageViews(std::span<ImageViewInOut> views) {
- FillImageViews<has_blacklists>(graphics_image_table, graphics_image_view_ids, views);
+ FillImageViews<has_blacklists>(channel_state->graphics_image_table,
+ channel_state->graphics_image_view_ids, views);
}
template <class P>
void TextureCache<P>::FillComputeImageViews(std::span<ImageViewInOut> views) {
- FillImageViews<true>(compute_image_table, compute_image_view_ids, views);
+ FillImageViews<true>(channel_state->compute_image_table, channel_state->compute_image_view_ids,
+ views);
}
template <class P>
typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) {
- if (index > graphics_sampler_table.Limit()) {
+ if (index > channel_state->graphics_sampler_table.Limit()) {
LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index);
return &slot_samplers[NULL_SAMPLER_ID];
}
- const auto [descriptor, is_new] = graphics_sampler_table.Read(index);
- SamplerId& id = graphics_sampler_ids[index];
+ const auto [descriptor, is_new] = channel_state->graphics_sampler_table.Read(index);
+ SamplerId& id = channel_state->graphics_sampler_ids[index];
if (is_new) {
id = FindSampler(descriptor);
}
@@ -151,12 +175,12 @@ typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) {
template <class P>
typename P::Sampler* TextureCache<P>::GetComputeSampler(u32 index) {
- if (index > compute_sampler_table.Limit()) {
+ if (index > channel_state->compute_sampler_table.Limit()) {
LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index);
return &slot_samplers[NULL_SAMPLER_ID];
}
- const auto [descriptor, is_new] = compute_sampler_table.Read(index);
- SamplerId& id = compute_sampler_ids[index];
+ const auto [descriptor, is_new] = channel_state->compute_sampler_table.Read(index);
+ SamplerId& id = channel_state->compute_sampler_ids[index];
if (is_new) {
id = FindSampler(descriptor);
}
@@ -166,34 +190,36 @@ typename P::Sampler* TextureCache<P>::GetComputeSampler(u32 index) {
template <class P>
void TextureCache<P>::SynchronizeGraphicsDescriptors() {
using SamplerIndex = Tegra::Engines::Maxwell3D::Regs::SamplerIndex;
- const bool linked_tsc = maxwell3d.regs.sampler_index == SamplerIndex::ViaHeaderIndex;
- const u32 tic_limit = maxwell3d.regs.tic.limit;
- const u32 tsc_limit = linked_tsc ? tic_limit : maxwell3d.regs.tsc.limit;
- if (graphics_sampler_table.Synchornize(maxwell3d.regs.tsc.Address(), tsc_limit)) {
- graphics_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID);
+ const bool linked_tsc = maxwell3d->regs.sampler_index == SamplerIndex::ViaHeaderIndex;
+ const u32 tic_limit = maxwell3d->regs.tic.limit;
+ const u32 tsc_limit = linked_tsc ? tic_limit : maxwell3d->regs.tsc.limit;
+ if (channel_state->graphics_sampler_table.Synchornize(maxwell3d->regs.tsc.Address(),
+ tsc_limit)) {
+ channel_state->graphics_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID);
}
- if (graphics_image_table.Synchornize(maxwell3d.regs.tic.Address(), tic_limit)) {
- graphics_image_view_ids.resize(tic_limit + 1, CORRUPT_ID);
+ if (channel_state->graphics_image_table.Synchornize(maxwell3d->regs.tic.Address(), tic_limit)) {
+ channel_state->graphics_image_view_ids.resize(tic_limit + 1, CORRUPT_ID);
}
}
template <class P>
void TextureCache<P>::SynchronizeComputeDescriptors() {
- const bool linked_tsc = kepler_compute.launch_description.linked_tsc;
- const u32 tic_limit = kepler_compute.regs.tic.limit;
- const u32 tsc_limit = linked_tsc ? tic_limit : kepler_compute.regs.tsc.limit;
- const GPUVAddr tsc_gpu_addr = kepler_compute.regs.tsc.Address();
- if (compute_sampler_table.Synchornize(tsc_gpu_addr, tsc_limit)) {
- compute_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID);
+ const bool linked_tsc = kepler_compute->launch_description.linked_tsc;
+ const u32 tic_limit = kepler_compute->regs.tic.limit;
+ const u32 tsc_limit = linked_tsc ? tic_limit : kepler_compute->regs.tsc.limit;
+ const GPUVAddr tsc_gpu_addr = kepler_compute->regs.tsc.Address();
+ if (channel_state->compute_sampler_table.Synchornize(tsc_gpu_addr, tsc_limit)) {
+ channel_state->compute_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID);
}
- if (compute_image_table.Synchornize(kepler_compute.regs.tic.Address(), tic_limit)) {
- compute_image_view_ids.resize(tic_limit + 1, CORRUPT_ID);
+ if (channel_state->compute_image_table.Synchornize(kepler_compute->regs.tic.Address(),
+ tic_limit)) {
+ channel_state->compute_image_view_ids.resize(tic_limit + 1, CORRUPT_ID);
}
}
template <class P>
bool TextureCache<P>::RescaleRenderTargets(bool is_clear) {
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
u32 scale_rating = 0;
bool rescaled = false;
std::array<ImageId, NUM_RT> tmp_color_images{};
@@ -290,7 +316,7 @@ bool TextureCache<P>::RescaleRenderTargets(bool is_clear) {
template <class P>
void TextureCache<P>::UpdateRenderTargets(bool is_clear) {
using namespace VideoCommon::Dirty;
- auto& flags = maxwell3d.dirty.flags;
+ auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::RenderTargets]) {
for (size_t index = 0; index < NUM_RT; ++index) {
ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index];
@@ -317,7 +343,7 @@ void TextureCache<P>::UpdateRenderTargets(bool is_clear) {
PrepareImageView(depth_buffer_id, true, is_clear && IsFullClear(depth_buffer_id));
for (size_t index = 0; index < NUM_RT; ++index) {
- render_targets.draw_buffers[index] = static_cast<u8>(maxwell3d.regs.rt_control.Map(index));
+ render_targets.draw_buffers[index] = static_cast<u8>(maxwell3d->regs.rt_control.Map(index));
}
u32 up_scale = 1;
u32 down_shift = 0;
@@ -326,9 +352,10 @@ void TextureCache<P>::UpdateRenderTargets(bool is_clear) {
down_shift = Settings::values.resolution_info.down_shift;
}
render_targets.size = Extent2D{
- (maxwell3d.regs.render_area.width * up_scale) >> down_shift,
- (maxwell3d.regs.render_area.height * up_scale) >> down_shift,
+ (maxwell3d->regs.render_area.width * up_scale) >> down_shift,
+ (maxwell3d->regs.render_area.height * up_scale) >> down_shift,
};
+ render_targets.is_rescaled = is_rescaling;
flags[Dirty::DepthBiasGlobal] = true;
}
@@ -343,7 +370,7 @@ template <bool has_blacklists>
void TextureCache<P>::FillImageViews(DescriptorTable<TICEntry>& table,
std::span<ImageViewId> cached_image_view_ids,
std::span<ImageViewInOut> views) {
- bool has_blacklisted;
+ bool has_blacklisted = false;
do {
has_deleted_images = false;
if constexpr (has_blacklists) {
@@ -433,7 +460,7 @@ void TextureCache<P>::DownloadMemory(VAddr cpu_addr, size_t size) {
const auto copies = FullDownloadCopies(image.info);
image.DownloadMemory(map, copies);
runtime.Finish();
- SwizzleImage(gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span);
+ SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span);
}
}
@@ -452,12 +479,20 @@ void TextureCache<P>::UnmapMemory(VAddr cpu_addr, size_t size) {
}
template <class P>
-void TextureCache<P>::UnmapGPUMemory(GPUVAddr gpu_addr, size_t size) {
+void TextureCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size) {
std::vector<ImageId> deleted_images;
- ForEachImageInRegionGPU(gpu_addr, size,
+ ForEachImageInRegionGPU(as_id, gpu_addr, size,
[&](ImageId id, Image&) { deleted_images.push_back(id); });
for (const ImageId id : deleted_images) {
Image& image = slot_images[id];
+ if (True(image.flags & ImageFlagBits::CpuModified)) {
+ return;
+ }
+ image.flags |= ImageFlagBits::CpuModified;
+ if (True(image.flags & ImageFlagBits::Tracked)) {
+ UntrackImage(image, id);
+ }
+ /*
if (True(image.flags & ImageFlagBits::Remapped)) {
continue;
}
@@ -465,6 +500,7 @@ void TextureCache<P>::UnmapGPUMemory(GPUVAddr gpu_addr, size_t size) {
if (True(image.flags & ImageFlagBits::Tracked)) {
UntrackImage(image, id);
}
+ */
}
}
@@ -564,7 +600,7 @@ void TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
template <class P>
typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_addr) {
// TODO: Properly implement this
- const auto it = page_table.find(cpu_addr >> PAGE_BITS);
+ const auto it = page_table.find(cpu_addr >> YUZU_PAGEBITS);
if (it == page_table.end()) {
return nullptr;
}
@@ -630,7 +666,7 @@ void TextureCache<P>::PopAsyncFlushes() {
for (const ImageId image_id : download_ids) {
const ImageBase& image = slot_images[image_id];
const auto copies = FullDownloadCopies(image.info);
- SwizzleImage(gpu_memory, image.gpu_addr, image.info, copies, download_span);
+ SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, download_span);
download_map.offset += image.unswizzled_size_bytes;
download_span = download_span.subspan(image.unswizzled_size_bytes);
}
@@ -689,26 +725,26 @@ void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging)
const GPUVAddr gpu_addr = image.gpu_addr;
if (True(image.flags & ImageFlagBits::AcceleratedUpload)) {
- gpu_memory.ReadBlockUnsafe(gpu_addr, mapped_span.data(), mapped_span.size_bytes());
+ gpu_memory->ReadBlockUnsafe(gpu_addr, mapped_span.data(), mapped_span.size_bytes());
const auto uploads = FullUploadSwizzles(image.info);
runtime.AccelerateImageUpload(image, staging, uploads);
} else if (True(image.flags & ImageFlagBits::Converted)) {
std::vector<u8> unswizzled_data(image.unswizzled_size_bytes);
- auto copies = UnswizzleImage(gpu_memory, gpu_addr, image.info, unswizzled_data);
+ auto copies = UnswizzleImage(*gpu_memory, gpu_addr, image.info, unswizzled_data);
ConvertImage(unswizzled_data, image.info, mapped_span, copies);
image.UploadMemory(staging, copies);
} else {
- const auto copies = UnswizzleImage(gpu_memory, gpu_addr, image.info, mapped_span);
+ const auto copies = UnswizzleImage(*gpu_memory, gpu_addr, image.info, mapped_span);
image.UploadMemory(staging, copies);
}
}
template <class P>
ImageViewId TextureCache<P>::FindImageView(const TICEntry& config) {
- if (!IsValidEntry(gpu_memory, config)) {
+ if (!IsValidEntry(*gpu_memory, config)) {
return NULL_IMAGE_VIEW_ID;
}
- const auto [pair, is_new] = image_views.try_emplace(config);
+ const auto [pair, is_new] = channel_state->image_views.try_emplace(config);
ImageViewId& image_view_id = pair->second;
if (is_new) {
image_view_id = CreateImageView(config);
@@ -752,9 +788,9 @@ ImageId TextureCache<P>::FindOrInsertImage(const ImageInfo& info, GPUVAddr gpu_a
template <class P>
ImageId TextureCache<P>::FindImage(const ImageInfo& info, GPUVAddr gpu_addr,
RelaxedOptions options) {
- std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
- cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr, CalculateGuestSizeInBytes(info));
+ cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr, CalculateGuestSizeInBytes(info));
if (!cpu_addr) {
return ImageId{};
}
@@ -835,7 +871,7 @@ void TextureCache<P>::InvalidateScale(Image& image) {
image.scale_tick = frame_tick + 1;
}
const std::span<const ImageViewId> image_view_ids = image.image_view_ids;
- auto& dirty = maxwell3d.dirty.flags;
+ auto& dirty = maxwell3d->dirty.flags;
dirty[Dirty::RenderTargets] = true;
dirty[Dirty::ZetaBuffer] = true;
for (size_t rt = 0; rt < NUM_RT; ++rt) {
@@ -855,12 +891,15 @@ void TextureCache<P>::InvalidateScale(Image& image) {
}
image.image_view_ids.clear();
image.image_view_infos.clear();
- if constexpr (ENABLE_VALIDATION) {
- std::ranges::fill(graphics_image_view_ids, CORRUPT_ID);
- std::ranges::fill(compute_image_view_ids, CORRUPT_ID);
+ for (size_t c : active_channel_ids) {
+ auto& channel_info = channel_storage[c];
+ if constexpr (ENABLE_VALIDATION) {
+ std::ranges::fill(channel_info.graphics_image_view_ids, CORRUPT_ID);
+ std::ranges::fill(channel_info.compute_image_view_ids, CORRUPT_ID);
+ }
+ channel_info.graphics_image_table.Invalidate();
+ channel_info.compute_image_table.Invalidate();
}
- graphics_image_table.Invalidate();
- compute_image_table.Invalidate();
has_deleted_images = true;
}
@@ -904,10 +943,10 @@ bool TextureCache<P>::ScaleDown(Image& image) {
template <class P>
ImageId TextureCache<P>::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
RelaxedOptions options) {
- std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
const auto size = CalculateGuestSizeInBytes(info);
- cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr, size);
+ cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr, size);
if (!cpu_addr) {
const VAddr fake_addr = ~(1ULL << 40ULL) + virtual_invalid_space;
virtual_invalid_space += Common::AlignUp(size, 32);
@@ -1025,7 +1064,7 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
const ImageId new_image_id = slot_images.insert(runtime, new_info, gpu_addr, cpu_addr);
Image& new_image = slot_images[new_image_id];
- if (!gpu_memory.IsContinousRange(new_image.gpu_addr, new_image.guest_size_bytes)) {
+ if (!gpu_memory->IsContinousRange(new_image.gpu_addr, new_image.guest_size_bytes)) {
new_image.flags |= ImageFlagBits::Sparse;
}
@@ -1052,6 +1091,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 {
@@ -1164,7 +1206,7 @@ SamplerId TextureCache<P>::FindSampler(const TSCEntry& config) {
if (std::ranges::all_of(config.raw, [](u64 value) { return value == 0; })) {
return NULL_SAMPLER_ID;
}
- const auto [pair, is_new] = samplers.try_emplace(config);
+ const auto [pair, is_new] = channel_state->samplers.try_emplace(config);
if (is_new) {
pair->second = slot_samplers.insert(runtime, config);
}
@@ -1173,7 +1215,7 @@ SamplerId TextureCache<P>::FindSampler(const TSCEntry& config) {
template <class P>
ImageViewId TextureCache<P>::FindColorBuffer(size_t index, bool is_clear) {
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
if (index >= regs.rt_control.count) {
return ImageViewId{};
}
@@ -1191,7 +1233,7 @@ ImageViewId TextureCache<P>::FindColorBuffer(size_t index, bool is_clear) {
template <class P>
ImageViewId TextureCache<P>::FindDepthBuffer(bool is_clear) {
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
if (!regs.zeta_enable) {
return ImageViewId{};
}
@@ -1288,11 +1330,17 @@ void TextureCache<P>::ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& f
template <class P>
template <typename Func>
-void TextureCache<P>::ForEachImageInRegionGPU(GPUVAddr gpu_addr, size_t size, Func&& func) {
+void TextureCache<P>::ForEachImageInRegionGPU(size_t as_id, GPUVAddr gpu_addr, size_t size,
+ Func&& func) {
using FuncReturn = typename std::invoke_result<Func, ImageId, Image&>::type;
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
boost::container::small_vector<ImageId, 8> images;
- ForEachGPUPage(gpu_addr, size, [this, &images, gpu_addr, size, func](u64 page) {
+ auto storage_id = getStorageID(as_id);
+ if (!storage_id) {
+ return;
+ }
+ auto& gpu_page_table = gpu_page_table_storage[*storage_id];
+ ForEachGPUPage(gpu_addr, size, [this, gpu_page_table, &images, gpu_addr, size, func](u64 page) {
const auto it = gpu_page_table.find(page);
if (it == gpu_page_table.end()) {
if constexpr (BOOL_BREAK) {
@@ -1375,9 +1423,9 @@ template <typename Func>
void TextureCache<P>::ForEachSparseSegment(ImageBase& image, Func&& func) {
using FuncReturn = typename std::invoke_result<Func, GPUVAddr, VAddr, size_t>::type;
static constexpr bool RETURNS_BOOL = std::is_same_v<FuncReturn, bool>;
- const auto segments = gpu_memory.GetSubmappedRange(image.gpu_addr, image.guest_size_bytes);
+ const auto segments = gpu_memory->GetSubmappedRange(image.gpu_addr, image.guest_size_bytes);
for (const auto& [gpu_addr, size] : segments) {
- std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
ASSERT(cpu_addr);
if constexpr (RETURNS_BOOL) {
if (func(gpu_addr, *cpu_addr, size)) {
@@ -1414,10 +1462,15 @@ 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,
- [this, image_id](u64 page) { gpu_page_table[page].push_back(image_id); });
+ ForEachGPUPage(image.gpu_addr, image.guest_size_bytes, [this, image_id](u64 page) {
+ (*channel_state->gpu_page_table)[page].push_back(image_id);
+ });
if (False(image.flags & ImageFlagBits::Sparse)) {
auto map_id =
slot_map_views.insert(image.gpu_addr, image.cpu_addr, image.guest_size_bytes, image_id);
@@ -1448,38 +1501,39 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) {
image.flags &= ~ImageFlagBits::BadOverlap;
lru_cache.Free(image.lru_index);
const auto& clear_page_table =
- [this, image_id](
- u64 page,
- std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>>& selected_page_table) {
+ [this, image_id](u64 page,
+ std::unordered_map<u64, std::vector<ImageId>, Common::IdentityHash<u64>>&
+ selected_page_table) {
const auto page_it = selected_page_table.find(page);
if (page_it == selected_page_table.end()) {
- UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PAGE_BITS);
+ ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS);
return;
}
std::vector<ImageId>& image_ids = page_it->second;
const auto vector_it = std::ranges::find(image_ids, image_id);
if (vector_it == image_ids.end()) {
- UNREACHABLE_MSG("Unregistering unregistered image in page=0x{:x}",
- page << PAGE_BITS);
+ ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}",
+ page << YUZU_PAGEBITS);
return;
}
image_ids.erase(vector_it);
};
- ForEachGPUPage(image.gpu_addr, image.guest_size_bytes,
- [this, &clear_page_table](u64 page) { clear_page_table(page, gpu_page_table); });
+ ForEachGPUPage(image.gpu_addr, image.guest_size_bytes, [this, &clear_page_table](u64 page) {
+ clear_page_table(page, (*channel_state->gpu_page_table));
+ });
if (False(image.flags & ImageFlagBits::Sparse)) {
const auto map_id = image.map_view_id;
ForEachCPUPage(image.cpu_addr, image.guest_size_bytes, [this, map_id](u64 page) {
const auto page_it = page_table.find(page);
if (page_it == page_table.end()) {
- UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PAGE_BITS);
+ ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS);
return;
}
std::vector<ImageMapId>& image_map_ids = page_it->second;
const auto vector_it = std::ranges::find(image_map_ids, map_id);
if (vector_it == image_map_ids.end()) {
- UNREACHABLE_MSG("Unregistering unregistered image in page=0x{:x}",
- page << PAGE_BITS);
+ ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}",
+ page << YUZU_PAGEBITS);
return;
}
image_map_ids.erase(vector_it);
@@ -1500,7 +1554,7 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) {
ForEachCPUPage(cpu_addr, size, [this, image_id](u64 page) {
const auto page_it = page_table.find(page);
if (page_it == page_table.end()) {
- UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PAGE_BITS);
+ ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS);
return;
}
std::vector<ImageMapId>& image_map_ids = page_it->second;
@@ -1584,22 +1638,22 @@ void TextureCache<P>::DeleteImage(ImageId image_id, bool immediate_delete) {
const GPUVAddr gpu_addr = image.gpu_addr;
const auto alloc_it = image_allocs_table.find(gpu_addr);
if (alloc_it == image_allocs_table.end()) {
- UNREACHABLE_MSG("Trying to delete an image alloc that does not exist in address 0x{:x}",
- gpu_addr);
+ ASSERT_MSG(false, "Trying to delete an image alloc that does not exist in address 0x{:x}",
+ gpu_addr);
return;
}
const ImageAllocId alloc_id = alloc_it->second;
std::vector<ImageId>& alloc_images = slot_image_allocs[alloc_id].images;
const auto alloc_image_it = std::ranges::find(alloc_images, image_id);
if (alloc_image_it == alloc_images.end()) {
- UNREACHABLE_MSG("Trying to delete an image that does not exist");
+ ASSERT_MSG(false, "Trying to delete an image that does not exist");
return;
}
ASSERT_MSG(False(image.flags & ImageFlagBits::Tracked), "Image was not untracked");
ASSERT_MSG(False(image.flags & ImageFlagBits::Registered), "Image was not unregistered");
// Mark render targets as dirty
- auto& dirty = maxwell3d.dirty.flags;
+ auto& dirty = maxwell3d->dirty.flags;
dirty[Dirty::RenderTargets] = true;
dirty[Dirty::ZetaBuffer] = true;
for (size_t rt = 0; rt < NUM_RT; ++rt) {
@@ -1649,24 +1703,30 @@ void TextureCache<P>::DeleteImage(ImageId image_id, bool immediate_delete) {
if (alloc_images.empty()) {
image_allocs_table.erase(alloc_it);
}
- if constexpr (ENABLE_VALIDATION) {
- std::ranges::fill(graphics_image_view_ids, CORRUPT_ID);
- std::ranges::fill(compute_image_view_ids, CORRUPT_ID);
+ for (size_t c : active_channel_ids) {
+ auto& channel_info = channel_storage[c];
+ if constexpr (ENABLE_VALIDATION) {
+ std::ranges::fill(channel_info.graphics_image_view_ids, CORRUPT_ID);
+ std::ranges::fill(channel_info.compute_image_view_ids, CORRUPT_ID);
+ }
+ channel_info.graphics_image_table.Invalidate();
+ channel_info.compute_image_table.Invalidate();
}
- graphics_image_table.Invalidate();
- compute_image_table.Invalidate();
has_deleted_images = true;
}
template <class P>
void TextureCache<P>::RemoveImageViewReferences(std::span<const ImageViewId> removed_views) {
- auto it = image_views.begin();
- while (it != image_views.end()) {
- const auto found = std::ranges::find(removed_views, it->second);
- if (found != removed_views.end()) {
- it = image_views.erase(it);
- } else {
- ++it;
+ for (size_t c : active_channel_ids) {
+ auto& channel_info = channel_storage[c];
+ auto it = channel_info.image_views.begin();
+ while (it != channel_info.image_views.end()) {
+ const auto found = std::ranges::find(removed_views, it->second);
+ if (found != removed_views.end()) {
+ it = channel_info.image_views.erase(it);
+ } else {
+ ++it;
+ }
}
}
}
@@ -1697,6 +1757,7 @@ void TextureCache<P>::SynchronizeAliases(ImageId image_id) {
boost::container::small_vector<const AliasedImage*, 1> aliased_images;
Image& image = slot_images[image_id];
bool any_rescaled = True(image.flags & ImageFlagBits::Rescaled);
+ bool any_modified = True(image.flags & ImageFlagBits::GpuModified);
u64 most_recent_tick = image.modification_tick;
for (const AliasedImage& aliased : image.aliased_images) {
ImageBase& aliased_image = slot_images[aliased.id];
@@ -1704,6 +1765,7 @@ 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);
+ any_modified |= True(aliased_image.flags & ImageFlagBits::GpuModified);
}
}
if (aliased_images.empty()) {
@@ -1718,6 +1780,9 @@ void TextureCache<P>::SynchronizeAliases(ImageId image_id) {
}
}
image.modification_tick = most_recent_tick;
+ if (any_modified) {
+ image.flags |= ImageFlagBits::GpuModified;
+ }
std::ranges::sort(aliased_images, [this](const AliasedImage* lhs, const AliasedImage* rhs) {
const ImageBase& lhs_image = slot_images[lhs->id];
const ImageBase& rhs_image = slot_images[rhs->id];
@@ -1725,7 +1790,7 @@ void TextureCache<P>::SynchronizeAliases(ImageId image_id) {
});
const auto& resolution = Settings::values.resolution_info;
for (const AliasedImage* const aliased : aliased_images) {
- if (!resolution.active | !any_rescaled) {
+ if (!resolution.active || !any_rescaled) {
CopyImage(image_id, aliased->id, aliased->copies);
continue;
}
@@ -1736,19 +1801,7 @@ void TextureCache<P>::SynchronizeAliases(ImageId image_id) {
continue;
}
ScaleUp(aliased_image);
-
- const bool both_2d{image.info.type == ImageType::e2D &&
- aliased_image.info.type == ImageType::e2D};
- auto copies = aliased->copies;
- for (auto copy : copies) {
- copy.extent.width = std::max<u32>(
- (copy.extent.width * resolution.up_scale) >> resolution.down_shift, 1);
- if (both_2d) {
- copy.extent.height = std::max<u32>(
- (copy.extent.height * resolution.up_scale) >> resolution.down_shift, 1);
- }
- }
- CopyImage(image_id, aliased->id, copies);
+ CopyImage(image_id, aliased->id, aliased->copies);
}
}
@@ -1908,6 +1961,7 @@ std::pair<FramebufferId, ImageViewId> TextureCache<P>::RenderTargetFromImage(
.color_buffer_ids = {color_view_id},
.depth_buffer_id = depth_view_id,
.size = {extent.width >> samples_x, extent.height >> samples_y},
+ .is_rescaled = is_rescaled,
});
return {framebuffer_id, view_id};
}
@@ -1920,7 +1974,7 @@ bool TextureCache<P>::IsFullClear(ImageViewId id) {
const ImageViewBase& image_view = slot_image_views[id];
const ImageBase& image = slot_images[image_view.image_id];
const Extent3D size = image_view.size;
- const auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d->regs;
const auto& scissor = regs.scissor_test[0];
if (image.info.resources.levels > 1 || image.info.resources.layers > 1) {
// Images with multiple resources can't be cleared in a single call
@@ -1935,4 +1989,19 @@ bool TextureCache<P>::IsFullClear(ImageViewId id) {
scissor.max_y >= size.height;
}
+template <class P>
+void TextureCache<P>::CreateChannel(struct Tegra::Control::ChannelState& channel) {
+ VideoCommon::ChannelSetupCaches<TextureCacheChannelInfo>::CreateChannel(channel);
+ const auto it = channel_map.find(channel.bind_id);
+ auto* this_state = &channel_storage[it->second];
+ const auto& this_as_ref = address_spaces[channel.memory_manager->GetID()];
+ this_state->gpu_page_table = &gpu_page_table_storage[this_as_ref.storage_id];
+}
+
+/// Bind a channel for execution.
+template <class P>
+void TextureCache<P>::OnGPUASRegister([[maybe_unused]] size_t map_id) {
+ gpu_page_table_storage.emplace_back();
+}
+
} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h
index 7107887a6..2fa8445eb 100644
--- a/src/video_core/texture_cache/texture_cache_base.h
+++ b/src/video_core/texture_cache/texture_cache_base.h
@@ -1,9 +1,10 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
+#include <deque>
+#include <limits>
#include <mutex>
#include <span>
#include <type_traits>
@@ -12,9 +13,11 @@
#include <queue>
#include "common/common_types.h"
+#include "common/hash.h"
#include "common/literals.h"
#include "common/lru_cache.h"
#include "video_core/compatible_formats.h"
+#include "video_core/control/channel_state_cache.h"
#include "video_core/delayed_destruction_ring.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/surface.h"
@@ -22,12 +25,15 @@
#include "video_core/texture_cache/image_base.h"
#include "video_core/texture_cache/image_info.h"
#include "video_core/texture_cache/image_view_base.h"
-#include "video_core/texture_cache/image_view_info.h"
#include "video_core/texture_cache/render_targets.h"
#include "video_core/texture_cache/slot_vector.h"
#include "video_core/texture_cache/types.h"
#include "video_core/textures/texture.h"
+namespace Tegra::Control {
+struct ChannelState;
+}
+
namespace VideoCommon {
using Tegra::Texture::SwizzleSource;
@@ -46,10 +52,37 @@ struct ImageViewInOut {
ImageViewId id{};
};
+using TextureCacheGPUMap = std::unordered_map<u64, std::vector<ImageId>, Common::IdentityHash<u64>>;
+
+class TextureCacheChannelInfo : public ChannelInfo {
+public:
+ TextureCacheChannelInfo() = delete;
+ TextureCacheChannelInfo(Tegra::Control::ChannelState& state) noexcept;
+ TextureCacheChannelInfo(const TextureCacheChannelInfo& state) = delete;
+ TextureCacheChannelInfo& operator=(const TextureCacheChannelInfo&) = delete;
+ TextureCacheChannelInfo(TextureCacheChannelInfo&& other) noexcept = default;
+ TextureCacheChannelInfo& operator=(TextureCacheChannelInfo&& other) noexcept = default;
+
+ DescriptorTable<TICEntry> graphics_image_table{gpu_memory};
+ DescriptorTable<TSCEntry> graphics_sampler_table{gpu_memory};
+ std::vector<SamplerId> graphics_sampler_ids;
+ std::vector<ImageViewId> graphics_image_view_ids;
+
+ DescriptorTable<TICEntry> compute_image_table{gpu_memory};
+ DescriptorTable<TSCEntry> compute_sampler_table{gpu_memory};
+ std::vector<SamplerId> compute_sampler_ids;
+ std::vector<ImageViewId> compute_image_view_ids;
+
+ std::unordered_map<TICEntry, ImageViewId> image_views;
+ std::unordered_map<TSCEntry, SamplerId> samplers;
+
+ TextureCacheGPUMap* gpu_page_table;
+};
+
template <class P>
-class TextureCache {
+class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelInfo> {
/// Address shift for caching images into a hash table
- static constexpr u64 PAGE_BITS = 20;
+ static constexpr u64 YUZU_PAGEBITS = 20;
/// Enables debugging features to the texture cache
static constexpr bool ENABLE_VALIDATION = P::ENABLE_VALIDATION;
@@ -60,8 +93,12 @@ 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 size_t UNSET_CHANNEL{std::numeric_limits<size_t>::max()};
+
+ 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;
@@ -77,16 +114,8 @@ class TextureCache {
PixelFormat src_format;
};
- template <typename T>
- struct IdentityHash {
- [[nodiscard]] size_t operator()(T value) const noexcept {
- return static_cast<size_t>(value);
- }
- };
-
public:
- explicit TextureCache(Runtime&, VideoCore::RasterizerInterface&, Tegra::Engines::Maxwell3D&,
- Tegra::Engines::KeplerCompute&, Tegra::MemoryManager&);
+ explicit TextureCache(Runtime&, VideoCore::RasterizerInterface&);
/// Notify the cache that a new frame has been queued
void TickFrame();
@@ -142,7 +171,7 @@ public:
void UnmapMemory(VAddr cpu_addr, size_t size);
/// Remove images in a region
- void UnmapGPUMemory(GPUVAddr gpu_addr, size_t size);
+ void UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size);
/// Blit an image with the given parameters
void BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
@@ -171,6 +200,9 @@ public:
[[nodiscard]] bool IsRescaling(const ImageViewBase& image_view) const noexcept;
+ /// Create channel state.
+ void CreateChannel(Tegra::Control::ChannelState& channel) final override;
+
std::mutex mutex;
private:
@@ -178,8 +210,8 @@ private:
template <typename Func>
static void ForEachCPUPage(VAddr addr, size_t size, Func&& func) {
static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>;
- const u64 page_end = (addr + size - 1) >> PAGE_BITS;
- for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) {
+ const u64 page_end = (addr + size - 1) >> YUZU_PAGEBITS;
+ for (u64 page = addr >> YUZU_PAGEBITS; page <= page_end; ++page) {
if constexpr (RETURNS_BOOL) {
if (func(page)) {
break;
@@ -193,8 +225,8 @@ private:
template <typename Func>
static void ForEachGPUPage(GPUVAddr addr, size_t size, Func&& func) {
static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>;
- const u64 page_end = (addr + size - 1) >> PAGE_BITS;
- for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) {
+ const u64 page_end = (addr + size - 1) >> YUZU_PAGEBITS;
+ for (u64 page = addr >> YUZU_PAGEBITS; page <= page_end; ++page) {
if constexpr (RETURNS_BOOL) {
if (func(page)) {
break;
@@ -205,6 +237,8 @@ private:
}
}
+ void OnGPUASRegister(size_t map_id) final override;
+
/// Runs the Garbage Collector.
void RunGarbageCollector();
@@ -273,7 +307,7 @@ private:
void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func);
template <typename Func>
- void ForEachImageInRegionGPU(GPUVAddr gpu_addr, size_t size, Func&& func);
+ void ForEachImageInRegionGPU(size_t as_id, GPUVAddr gpu_addr, size_t size, Func&& func);
template <typename Func>
void ForEachSparseImageInRegion(GPUVAddr gpu_addr, size_t size, Func&& func);
@@ -338,31 +372,16 @@ private:
u64 GetScaledImageSizeBytes(ImageBase& image);
Runtime& runtime;
- VideoCore::RasterizerInterface& rasterizer;
- Tegra::Engines::Maxwell3D& maxwell3d;
- Tegra::Engines::KeplerCompute& kepler_compute;
- Tegra::MemoryManager& gpu_memory;
- DescriptorTable<TICEntry> graphics_image_table{gpu_memory};
- DescriptorTable<TSCEntry> graphics_sampler_table{gpu_memory};
- std::vector<SamplerId> graphics_sampler_ids;
- std::vector<ImageViewId> graphics_image_view_ids;
-
- DescriptorTable<TICEntry> compute_image_table{gpu_memory};
- DescriptorTable<TSCEntry> compute_sampler_table{gpu_memory};
- std::vector<SamplerId> compute_sampler_ids;
- std::vector<ImageViewId> compute_image_view_ids;
+ VideoCore::RasterizerInterface& rasterizer;
+ std::deque<TextureCacheGPUMap> gpu_page_table_storage;
RenderTargets render_targets;
- std::unordered_map<TICEntry, ImageViewId> image_views;
- std::unordered_map<TSCEntry, SamplerId> samplers;
std::unordered_map<RenderTargets, FramebufferId> framebuffers;
- std::unordered_map<u64, std::vector<ImageMapId>, IdentityHash<u64>> page_table;
- std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> gpu_page_table;
- std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> sparse_page_table;
-
+ std::unordered_map<u64, std::vector<ImageMapId>, Common::IdentityHash<u64>> page_table;
+ std::unordered_map<u64, std::vector<ImageId>, Common::IdentityHash<u64>> sparse_page_table;
std::unordered_map<ImageId, std::vector<ImageViewId>> sparse_views;
VAddr virtual_invalid_space{};
@@ -373,6 +392,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/texture_cache/types.h b/src/video_core/texture_cache/types.h
index 5ac27b3a7..0453456b4 100644
--- a/src/video_core/texture_cache/types.h
+++ b/src/video_core/texture_cache/types.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index d8e19cb2f..1223df5a0 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -1,31 +1,11 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-FileCopyrightText: Ryujinx Team and Contributors
+// SPDX-License-Identifier: GPL-2.0-or-later AND MIT
// This files contains code from Ryujinx
// A copy of the code can be obtained from https://github.com/Ryujinx/Ryujinx
// The sections using code from Ryujinx are marked with a link to the original version
-// MIT License
-//
-// Copyright (c) Ryujinx Team and Contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
-// associated documentation files (the "Software"), to deal in the Software without restriction,
-// including without limitation the rights to use, copy, modify, merge, publish, distribute,
-// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all copies or
-// substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
-// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
#include <algorithm>
#include <array>
#include <numeric>
@@ -537,7 +517,6 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr
const u32 host_bytes_per_layer = num_blocks_per_layer * bytes_per_block;
UNIMPLEMENTED_IF(info.tile_width_spacing > 0);
-
UNIMPLEMENTED_IF(copy.image_offset.x != 0);
UNIMPLEMENTED_IF(copy.image_offset.y != 0);
UNIMPLEMENTED_IF(copy.image_offset.z != 0);
@@ -775,7 +754,7 @@ bool IsValidEntry(const Tegra::MemoryManager& gpu_memory, const TICEntry& config
if (address == 0) {
return false;
}
- if (address > (1ULL << 48)) {
+ if (address >= (1ULL << 40)) {
return false;
}
if (gpu_memory.GpuToCpuAddress(address).has_value()) {
diff --git a/src/video_core/texture_cache/util.h b/src/video_core/texture_cache/util.h
index 7af52de2e..5e28f4ab3 100644
--- a/src/video_core/texture_cache/util.h
+++ b/src/video_core/texture_cache/util.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -9,10 +8,8 @@
#include "common/common_types.h"
-#include "video_core/engines/maxwell_3d.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/image_base.h"
-#include "video_core/texture_cache/image_view_base.h"
#include "video_core/texture_cache/types.h"
#include "video_core/textures/texture.h"
diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp
index 25161df1f..15b9d4182 100644
--- a/src/video_core/textures/astc.cpp
+++ b/src/video_core/textures/astc.cpp
@@ -1,21 +1,11 @@
-// Copyright 2016 The University of North Carolina at Chapel Hill
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
+// SPDX-FileCopyrightText: 2016 The University of North Carolina at Chapel Hill
+// SPDX-License-Identifier: Apache-2.0
+
// Please send all BUG REPORTS to <pavel@cs.unc.edu>.
// <http://gamma.cs.unc.edu/FasTC/>
#include <algorithm>
+#include <bit>
#include <cassert>
#include <cstring>
#include <span>
@@ -23,7 +13,9 @@
#include <boost/container/static_vector.hpp>
+#include "common/alignment.h"
#include "common/common_types.h"
+#include "common/thread_worker.h"
#include "video_core/textures/astc.h"
class InputBitStream {
@@ -1421,7 +1413,7 @@ static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 b
static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) {
for (u32 j = 0; j < blockHeight; j++) {
for (u32 i = 0; i < blockWidth; i++) {
- outBuf[j * blockWidth + i] = 0xFFFF00FF;
+ outBuf[j * blockWidth + i] = 0x00000000;
}
}
}
@@ -1660,29 +1652,41 @@ static void DecompressBlock(std::span<const u8, 16> inBuf, const u32 blockWidth,
void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth,
uint32_t block_width, uint32_t block_height, std::span<uint8_t> output) {
- u32 block_index = 0;
- std::size_t depth_offset = 0;
- for (u32 z = 0; z < depth; z++) {
- for (u32 y = 0; y < height; y += block_height) {
- for (u32 x = 0; x < width; x += block_width) {
- const std::span<const u8, 16> blockPtr{data.subspan(block_index * 16, 16)};
-
- // Blocks can be at most 12x12
- std::array<u32, 12 * 12> uncompData;
- DecompressBlock(blockPtr, block_width, block_height, uncompData);
-
- u32 decompWidth = std::min(block_width, width - x);
- u32 decompHeight = std::min(block_height, height - y);
-
- const std::span<u8> outRow = output.subspan(depth_offset + (y * width + x) * 4);
- for (u32 jj = 0; jj < decompHeight; jj++) {
- std::memcpy(outRow.data() + jj * width * 4,
- uncompData.data() + jj * block_width, decompWidth * 4);
+ const u32 rows = Common::DivideUp(height, block_height);
+ const u32 cols = Common::DivideUp(width, block_width);
+
+ Common::ThreadWorker workers{std::max(std::thread::hardware_concurrency(), 2U) / 2,
+ "ASTCDecompress"};
+
+ for (u32 z = 0; z < depth; ++z) {
+ const u32 depth_offset = z * height * width * 4;
+ for (u32 y_index = 0; y_index < rows; ++y_index) {
+ auto decompress_stride = [data, width, height, depth, block_width, block_height, output,
+ rows, cols, z, depth_offset, y_index] {
+ const u32 y = y_index * block_height;
+ for (u32 x_index = 0; x_index < cols; ++x_index) {
+ const u32 block_index = (z * rows * cols) + (y_index * cols) + x_index;
+ const u32 x = x_index * block_width;
+
+ const std::span<const u8, 16> blockPtr{data.subspan(block_index * 16, 16)};
+
+ // Blocks can be at most 12x12
+ std::array<u32, 12 * 12> uncompData;
+ DecompressBlock(blockPtr, block_width, block_height, uncompData);
+
+ u32 decompWidth = std::min(block_width, width - x);
+ u32 decompHeight = std::min(block_height, height - y);
+
+ const std::span<u8> outRow = output.subspan(depth_offset + (y * width + x) * 4);
+ for (u32 h = 0; h < decompHeight; ++h) {
+ std::memcpy(outRow.data() + h * width * 4,
+ uncompData.data() + h * block_width, decompWidth * 4);
+ }
}
- ++block_index;
- }
+ };
+ workers.QueueWork(std::move(decompress_stride));
}
- depth_offset += height * width * 4;
+ workers.WaitForRequests();
}
}
diff --git a/src/video_core/textures/astc.h b/src/video_core/textures/astc.h
index 14d2beec0..afd3933c3 100644
--- a/src/video_core/textures/astc.h
+++ b/src/video_core/textures/astc.h
@@ -1,12 +1,8 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <bit>
-#include "common/common_types.h"
-
namespace Tegra::Texture::ASTC {
void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth,
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index 24e943e4c..52d067a2d 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -1,12 +1,10 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <cmath>
#include <cstring>
#include <span>
-#include <utility>
#include "common/alignment.h"
#include "common/assert.h"
@@ -14,13 +12,30 @@
#include "common/div_ceil.h"
#include "video_core/gpu.h"
#include "video_core/textures/decoders.h"
-#include "video_core/textures/texture.h"
namespace Tegra::Texture {
namespace {
+template <u32 mask>
+constexpr u32 pdep(u32 value) {
+ u32 result = 0;
+ u32 m = mask;
+ for (u32 bit = 1; m; bit += bit) {
+ if (value & bit)
+ result |= m & -m;
+ m &= m - 1;
+ }
+ return result;
+}
+
+template <u32 mask, u32 incr_amount>
+void incrpdep(u32& value) {
+ constexpr u32 swizzled_incr = pdep<mask>(incr_amount);
+ value = ((value | ~mask) + swizzled_incr) & mask;
+}
+
template <bool TO_LINEAR, u32 BYTES_PER_PIXEL>
void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32 height, u32 depth,
- u32 block_height, u32 block_depth, u32 stride_alignment) {
+ u32 block_height, u32 block_depth, u32 stride) {
// The origin of the transformation can be configured here, leave it as zero as the current API
// doesn't expose it.
static constexpr u32 origin_x = 0;
@@ -30,7 +45,6 @@ void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32
// We can configure here a custom pitch
// As it's not exposed 'width * BYTES_PER_PIXEL' will be the expected pitch.
const u32 pitch = width * BYTES_PER_PIXEL;
- const u32 stride = Common::AlignUpLog2(width, stride_alignment) * BYTES_PER_PIXEL;
const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT);
const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth);
@@ -47,18 +61,20 @@ void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32
((z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height));
for (u32 line = 0; line < height; ++line) {
const u32 y = line + origin_y;
- const auto& table = SWIZZLE_TABLE[y % GOB_SIZE_Y];
+ const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(y);
const u32 block_y = y >> GOB_SIZE_Y_SHIFT;
const u32 offset_y = (block_y >> block_height) * block_size +
((block_y & block_height_mask) << GOB_SIZE_SHIFT);
- for (u32 column = 0; column < width; ++column) {
+ u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL);
+ for (u32 column = 0; column < width;
+ ++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
- const u32 swizzled_offset = base_swizzled_offset + table[x % GOB_SIZE_X];
+ const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
@@ -72,6 +88,69 @@ void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32
}
}
+template <bool TO_LINEAR, u32 BYTES_PER_PIXEL>
+void SwizzleSubrectImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32 height,
+ u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, u32 num_lines,
+ u32 block_height, u32 block_depth, u32 pitch_linear) {
+ // The origin of the transformation can be configured here, leave it as zero as the current API
+ // doesn't expose it.
+ static constexpr u32 origin_z = 0;
+
+ // We can configure here a custom pitch
+ // As it's not exposed 'width * BYTES_PER_PIXEL' will be the expected pitch.
+ const u32 pitch = pitch_linear;
+ const u32 stride = Common::AlignUpLog2(width * BYTES_PER_PIXEL, GOB_SIZE_X_SHIFT);
+
+ const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT);
+ const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth);
+ const u32 slice_size =
+ Common::DivCeilLog2(height, block_height + GOB_SIZE_Y_SHIFT) * block_size;
+
+ const u32 block_height_mask = (1U << block_height) - 1;
+ const u32 block_depth_mask = (1U << block_depth) - 1;
+ const u32 x_shift = GOB_SIZE_SHIFT + block_height + block_depth;
+
+ u32 unprocessed_lines = num_lines;
+ u32 extent_y = std::min(num_lines, height - origin_y);
+
+ for (u32 slice = 0; slice < depth; ++slice) {
+ const u32 z = slice + origin_z;
+ const u32 offset_z = (z >> block_depth) * slice_size +
+ ((z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height));
+ const u32 lines_in_y = std::min(unprocessed_lines, extent_y);
+ for (u32 line = 0; line < lines_in_y; ++line) {
+ const u32 y = line + origin_y;
+ const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(y);
+
+ const u32 block_y = y >> GOB_SIZE_Y_SHIFT;
+ const u32 offset_y = (block_y >> block_height) * block_size +
+ ((block_y & block_height_mask) << GOB_SIZE_SHIFT);
+
+ u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL);
+ for (u32 column = 0; column < extent_x;
+ ++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
+ const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
+ const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
+
+ const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
+ const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
+
+ const u32 unswizzled_offset =
+ slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
+
+ u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset];
+ const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset];
+
+ std::memcpy(dst, src, BYTES_PER_PIXEL);
+ }
+ }
+ unprocessed_lines -= lines_in_y;
+ if (unprocessed_lines == 0) {
+ return;
+ }
+ }
+}
+
template <bool TO_LINEAR>
void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width,
u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) {
@@ -90,118 +169,43 @@ void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixe
BPP_CASE(16)
#undef BPP_CASE
default:
- UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel);
- }
-}
-
-template <u32 BYTES_PER_PIXEL>
-void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
- u8* swizzled_data, const u8* unswizzled_data, u32 block_height_bit,
- u32 offset_x, u32 offset_y) {
- const u32 block_height = 1U << block_height_bit;
- const u32 image_width_in_gobs =
- (swizzled_width * BYTES_PER_PIXEL + (GOB_SIZE_X - 1)) / GOB_SIZE_X;
- for (u32 line = 0; line < subrect_height; ++line) {
- const u32 dst_y = line + offset_y;
- const u32 gob_address_y =
- (dst_y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs +
- ((dst_y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE;
- const auto& table = SWIZZLE_TABLE[dst_y % GOB_SIZE_Y];
- for (u32 x = 0; x < subrect_width; ++x) {
- const u32 dst_x = x + offset_x;
- const u32 gob_address =
- gob_address_y + (dst_x * BYTES_PER_PIXEL / GOB_SIZE_X) * GOB_SIZE * block_height;
- const u32 swizzled_offset = gob_address + table[(dst_x * BYTES_PER_PIXEL) % GOB_SIZE_X];
- const u32 unswizzled_offset = line * source_pitch + x * BYTES_PER_PIXEL;
-
- const u8* const source_line = unswizzled_data + unswizzled_offset;
- u8* const dest_addr = swizzled_data + swizzled_offset;
- std::memcpy(dest_addr, source_line, BYTES_PER_PIXEL);
- }
+ ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel);
}
}
-template <u32 BYTES_PER_PIXEL>
-void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 block_height,
- u32 origin_x, u32 origin_y, u8* output, const u8* input) {
- const u32 stride = width * BYTES_PER_PIXEL;
- const u32 gobs_in_x = (stride + GOB_SIZE_X - 1) / GOB_SIZE_X;
- const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height);
-
- const u32 block_height_mask = (1U << block_height) - 1;
- const u32 x_shift = GOB_SIZE_SHIFT + block_height;
-
- for (u32 line = 0; line < line_count; ++line) {
- const u32 src_y = line + origin_y;
- const auto& table = SWIZZLE_TABLE[src_y % GOB_SIZE_Y];
-
- const u32 block_y = src_y >> GOB_SIZE_Y_SHIFT;
- const u32 src_offset_y = (block_y >> block_height) * block_size +
- ((block_y & block_height_mask) << GOB_SIZE_SHIFT);
- for (u32 column = 0; column < line_length_in; ++column) {
- const u32 src_x = (column + origin_x) * BYTES_PER_PIXEL;
- const u32 src_offset_x = (src_x >> GOB_SIZE_X_SHIFT) << x_shift;
-
- const u32 swizzled_offset = src_offset_y + src_offset_x + table[src_x % GOB_SIZE_X];
- const u32 unswizzled_offset = line * pitch + column * BYTES_PER_PIXEL;
-
- std::memcpy(output + unswizzled_offset, input + swizzled_offset, BYTES_PER_PIXEL);
- }
- }
-}
-
-template <u32 BYTES_PER_PIXEL>
-void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 height,
- u32 block_height, u32 block_depth, u32 origin_x, u32 origin_y, u8* output,
- const u8* input) {
- UNIMPLEMENTED_IF(origin_x > 0);
- UNIMPLEMENTED_IF(origin_y > 0);
-
- const u32 stride = width * BYTES_PER_PIXEL;
- const u32 gobs_in_x = (stride + GOB_SIZE_X - 1) / GOB_SIZE_X;
- const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth);
-
- const u32 block_height_mask = (1U << block_height) - 1;
- const u32 x_shift = static_cast<u32>(GOB_SIZE_SHIFT) + block_height + block_depth;
-
- for (u32 line = 0; line < line_count; ++line) {
- const auto& table = SWIZZLE_TABLE[line % GOB_SIZE_Y];
- const u32 block_y = line / GOB_SIZE_Y;
- const u32 dst_offset_y =
- (block_y >> block_height) * block_size + (block_y & block_height_mask) * GOB_SIZE;
- for (u32 x = 0; x < line_length_in; ++x) {
- const u32 dst_offset =
- ((x / GOB_SIZE_X) << x_shift) + dst_offset_y + table[x % GOB_SIZE_X];
- const u32 src_offset = x * BYTES_PER_PIXEL + line * pitch;
- std::memcpy(output + dst_offset, input + src_offset, BYTES_PER_PIXEL);
- }
- }
-}
} // Anonymous namespace
void UnswizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth,
u32 stride_alignment) {
+ const u32 stride = Common::AlignUpLog2(width, stride_alignment) * bytes_per_pixel;
+ const u32 new_bpp = std::min(4U, static_cast<u32>(std::countr_zero(width * bytes_per_pixel)));
+ width = (width * bytes_per_pixel) >> new_bpp;
+ bytes_per_pixel = 1U << new_bpp;
Swizzle<false>(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth,
- stride_alignment);
+ stride);
}
void SwizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width,
u32 height, u32 depth, u32 block_height, u32 block_depth,
u32 stride_alignment) {
+ const u32 stride = Common::AlignUpLog2(width, stride_alignment) * bytes_per_pixel;
+ const u32 new_bpp = std::min(4U, static_cast<u32>(std::countr_zero(width * bytes_per_pixel)));
+ width = (width * bytes_per_pixel) >> new_bpp;
+ bytes_per_pixel = 1U << new_bpp;
Swizzle<true>(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth,
- stride_alignment);
+ stride);
}
-void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, u8* swizzled_data, const u8* unswizzled_data,
- u32 block_height_bit, u32 offset_x, u32 offset_y) {
+void SwizzleSubrect(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width,
+ u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, u32 extent_y,
+ u32 block_height, u32 block_depth, u32 pitch_linear) {
switch (bytes_per_pixel) {
#define BPP_CASE(x) \
case x: \
- return SwizzleSubrect<x>(subrect_width, subrect_height, source_pitch, swizzled_width, \
- swizzled_data, unswizzled_data, block_height_bit, offset_x, \
- offset_y);
+ return SwizzleSubrectImpl<true, x>(output, input, width, height, depth, origin_x, \
+ origin_y, extent_x, extent_y, block_height, \
+ block_depth, pitch_linear);
BPP_CASE(1)
BPP_CASE(2)
BPP_CASE(3)
@@ -212,17 +216,19 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32
BPP_CASE(16)
#undef BPP_CASE
default:
- UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel);
+ ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel);
}
}
-void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 bytes_per_pixel,
- u32 block_height, u32 origin_x, u32 origin_y, u8* output, const u8* input) {
+void UnswizzleSubrect(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
+ u32 width, u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x,
+ u32 extent_y, u32 block_height, u32 block_depth, u32 pitch_linear) {
switch (bytes_per_pixel) {
#define BPP_CASE(x) \
case x: \
- return UnswizzleSubrect<x>(line_length_in, line_count, pitch, width, block_height, \
- origin_x, origin_y, output, input);
+ return SwizzleSubrectImpl<false, x>(output, input, width, height, depth, origin_x, \
+ origin_y, extent_x, extent_y, block_height, \
+ block_depth, pitch_linear);
BPP_CASE(1)
BPP_CASE(2)
BPP_CASE(3)
@@ -233,54 +239,7 @@ void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width,
BPP_CASE(16)
#undef BPP_CASE
default:
- UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel);
- }
-}
-
-void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 height,
- u32 bytes_per_pixel, u32 block_height, u32 block_depth, u32 origin_x,
- u32 origin_y, u8* output, const u8* input) {
- switch (bytes_per_pixel) {
-#define BPP_CASE(x) \
- case x: \
- return SwizzleSliceToVoxel<x>(line_length_in, line_count, pitch, width, height, \
- block_height, block_depth, origin_x, origin_y, output, \
- input);
- BPP_CASE(1)
- BPP_CASE(2)
- BPP_CASE(3)
- BPP_CASE(4)
- BPP_CASE(6)
- BPP_CASE(8)
- BPP_CASE(12)
- BPP_CASE(16)
-#undef BPP_CASE
- default:
- UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel);
- }
-}
-
-void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 dst_y,
- const u32 block_height_bit, const std::size_t copy_size, const u8* source_data,
- u8* swizzle_data) {
- const u32 block_height = 1U << block_height_bit;
- const u32 image_width_in_gobs{(width + GOB_SIZE_X - 1) / GOB_SIZE_X};
- std::size_t count = 0;
- for (std::size_t y = dst_y; y < height && count < copy_size; ++y) {
- const std::size_t gob_address_y =
- (y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs +
- ((y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE;
- const auto& table = SWIZZLE_TABLE[y % GOB_SIZE_Y];
- for (std::size_t x = dst_x; x < width && count < copy_size; ++x) {
- const std::size_t gob_address =
- gob_address_y + (x / GOB_SIZE_X) * GOB_SIZE * block_height;
- const std::size_t swizzled_offset = gob_address + table[x % GOB_SIZE_X];
- const u8* source_line = source_data + count;
- u8* dest_addr = swizzle_data + swizzled_offset;
- count++;
-
- *dest_addr = *source_line;
- }
+ ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel);
}
}
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
index 4c14cefbf..e70407692 100644
--- a/src/video_core/textures/decoders.h
+++ b/src/video_core/textures/decoders.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -21,6 +20,9 @@ constexpr u32 GOB_SIZE_Y_SHIFT = 3;
constexpr u32 GOB_SIZE_Z_SHIFT = 0;
constexpr u32 GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT;
+constexpr u32 SWIZZLE_X_BITS = 0b100101111;
+constexpr u32 SWIZZLE_Y_BITS = 0b011010000;
+
using SwizzleTable = std::array<std::array<u32, GOB_SIZE_X>, GOB_SIZE_Y>;
/**
@@ -38,7 +40,6 @@ constexpr SwizzleTable MakeSwizzleTable() {
}
return table;
}
-constexpr SwizzleTable SWIZZLE_TABLE = MakeSwizzleTable();
/// Unswizzles a block linear texture into linear memory.
void UnswizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
@@ -55,34 +56,14 @@ std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height
u32 block_height, u32 block_depth);
/// Copies an untiled subrectangle into a tiled surface.
-void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, u8* swizzled_data, const u8* unswizzled_data,
- u32 block_height_bit, u32 offset_x, u32 offset_y);
+void SwizzleSubrect(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width,
+ u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, u32 extent_y,
+ u32 block_height, u32 block_depth, u32 pitch_linear);
/// Copies a tiled subrectangle into a linear surface.
-void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 bytes_per_pixel,
- u32 block_height, u32 origin_x, u32 origin_y, u8* output, const u8* input);
-
-/// @brief Swizzles a 2D array of pixels into a 3D texture
-/// @param line_length_in Number of pixels per line
-/// @param line_count Number of lines
-/// @param pitch Number of bytes per line
-/// @param width Width of the swizzled texture
-/// @param height Height of the swizzled texture
-/// @param bytes_per_pixel Number of bytes used per pixel
-/// @param block_height Block height shift
-/// @param block_depth Block depth shift
-/// @param origin_x Column offset in pixels of the swizzled texture
-/// @param origin_y Row offset in pixels of the swizzled texture
-/// @param output Pointer to the pixels of the swizzled texture
-/// @param input Pointer to the 2D array of pixels used as input
-/// @pre input and output points to an array large enough to hold the number of bytes used
-void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 height,
- u32 bytes_per_pixel, u32 block_height, u32 block_depth, u32 origin_x,
- u32 origin_y, u8* output, const u8* input);
-
-void SwizzleKepler(u32 width, u32 height, u32 dst_x, u32 dst_y, u32 block_height,
- std::size_t copy_size, const u8* source_data, u8* swizzle_data);
+void UnswizzleSubrect(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
+ u32 width, u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x,
+ u32 extent_y, u32 block_height, u32 block_depth, u32 pitch_linear);
/// Obtains the offset of the gob for positions 'dst_x' & 'dst_y'
u64 GetGOBOffset(u32 width, u32 height, u32 dst_x, u32 dst_y, u32 block_height,
diff --git a/src/video_core/textures/texture.cpp b/src/video_core/textures/texture.cpp
index 06954963d..b8327c88a 100644
--- a/src/video_core/textures/texture.cpp
+++ b/src/video_core/textures/texture.cpp
@@ -1,8 +1,6 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include <algorithm>
#include <array>
#include "common/cityhash.h"
diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h
index 7994cb859..7c4553a53 100644
--- a/src/video_core/textures/texture.h
+++ b/src/video_core/textures/texture.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/transform_feedback.cpp b/src/video_core/transform_feedback.cpp
index ba26ac3f1..7e605981c 100644
--- a/src/video_core/transform_feedback.cpp
+++ b/src/video_core/transform_feedback.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
diff --git a/src/video_core/transform_feedback.h b/src/video_core/transform_feedback.h
index 8f6946d65..a519adb59 100644
--- a/src/video_core/transform_feedback.h
+++ b/src/video_core/transform_feedback.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 329bf4def..04ac4af11 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
@@ -50,6 +49,7 @@ std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Cor
gpu->BindRenderer(std::move(renderer));
return gpu;
} catch (const std::runtime_error& exception) {
+ scope.Cancel();
LOG_ERROR(HW_GPU, "Failed to initialize GPU: {}", exception.what());
return nullptr;
}
diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h
index 084df641f..f8e2444f3 100644
--- a/src/video_core/video_core.h
+++ b/src/video_core/video_core.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
index fdd1a5081..85f1d13e0 100644
--- a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
+++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef HAS_NSIGHT_AFTERMATH
diff --git a/src/video_core/vulkan_common/nsight_aftermath_tracker.h b/src/video_core/vulkan_common/nsight_aftermath_tracker.h
index eae1891dd..9c1fb7720 100644
--- a/src/video_core/vulkan_common/nsight_aftermath_tracker.h
+++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.h
@@ -1,23 +1,24 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <filesystem>
-#include <mutex>
#include <span>
-#include <string>
-#include <vector>
#include "common/common_types.h"
-#include "common/dynamic_library.h"
-#include "video_core/vulkan_common/vulkan_wrapper.h"
#ifdef HAS_NSIGHT_AFTERMATH
+#include <filesystem>
+#include <mutex>
+
+// Vulkan headers must be included before Aftermath
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
#include <GFSDK_Aftermath_Defines.h>
#include <GFSDK_Aftermath_GpuCrashDump.h>
#include <GFSDK_Aftermath_GpuCrashDumpDecoding.h>
+
+#include "common/dynamic_library.h"
#endif
namespace Vulkan {
diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.cpp b/src/video_core/vulkan_common/vulkan_debug_callback.cpp
index cf94e1d39..736474009 100644
--- a/src/video_core/vulkan_common/vulkan_debug_callback.cpp
+++ b/src/video_core/vulkan_common/vulkan_debug_callback.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
#include "common/logging/log.h"
diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.h b/src/video_core/vulkan_common/vulkan_debug_callback.h
index b0519f132..71b1f69ec 100644
--- a/src/video_core/vulkan_common/vulkan_debug_callback.h
+++ b/src/video_core/vulkan_common/vulkan_debug_callback.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 153702c0b..ddecfca13 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -1,24 +1,24 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <bitset>
#include <chrono>
#include <optional>
-#include <string_view>
#include <thread>
#include <unordered_set>
#include <utility>
#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{
@@ -39,6 +39,32 @@ constexpr std::array DEPTH16_UNORM_STENCIL8_UINT{
VK_FORMAT_D32_SFLOAT_S8_UINT,
VK_FORMAT_UNDEFINED,
};
+
+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,
+};
+
+constexpr std::array R16G16B16_SFLOAT{
+ VK_FORMAT_R16G16B16A16_SFLOAT,
+ VK_FORMAT_UNDEFINED,
+};
+
+constexpr std::array R16G16B16_SSCALED{
+ VK_FORMAT_R16G16B16A16_SSCALED,
+ VK_FORMAT_UNDEFINED,
+};
+
+constexpr std::array R8G8B8_SSCALED{
+ VK_FORMAT_R8G8B8A8_SSCALED,
+ VK_FORMAT_UNDEFINED,
+};
+
} // namespace Alternatives
enum class NvidiaArchitecture {
@@ -87,6 +113,16 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
return Alternatives::DEPTH24_UNORM_STENCIL8_UINT.data();
case VK_FORMAT_D16_UNORM_S8_UINT:
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();
+ case VK_FORMAT_R16G16B16_SFLOAT:
+ return Alternatives::R16G16B16_SFLOAT.data();
+ case VK_FORMAT_R16G16B16_SSCALED:
+ return Alternatives::R16G16B16_SSCALED.data();
+ case VK_FORMAT_R8G8B8_SSCALED:
+ return Alternatives::R8G8B8_SSCALED.data();
default:
return nullptr;
}
@@ -107,105 +143,142 @@ VkFormatFeatureFlags GetFormatFeatures(VkFormatProperties properties, FormatType
std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::PhysicalDevice physical) {
static constexpr std::array formats{
- VK_FORMAT_A8B8G8R8_UNORM_PACK32,
- VK_FORMAT_A8B8G8R8_UINT_PACK32,
- VK_FORMAT_A8B8G8R8_SNORM_PACK32,
+ VK_FORMAT_A1R5G5B5_UNORM_PACK16,
+ VK_FORMAT_A2B10G10R10_SINT_PACK32,
+ VK_FORMAT_A2B10G10R10_SNORM_PACK32,
+ VK_FORMAT_A2B10G10R10_SSCALED_PACK32,
+ VK_FORMAT_A2B10G10R10_UINT_PACK32,
+ VK_FORMAT_A2B10G10R10_UNORM_PACK32,
+ VK_FORMAT_A2B10G10R10_USCALED_PACK32,
VK_FORMAT_A8B8G8R8_SINT_PACK32,
+ VK_FORMAT_A8B8G8R8_SNORM_PACK32,
VK_FORMAT_A8B8G8R8_SRGB_PACK32,
- VK_FORMAT_R5G6B5_UNORM_PACK16,
+ VK_FORMAT_A8B8G8R8_UINT_PACK32,
+ VK_FORMAT_A8B8G8R8_UNORM_PACK32,
+ VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
+ VK_FORMAT_ASTC_10x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x5_UNORM_BLOCK,
+ VK_FORMAT_ASTC_10x6_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x6_UNORM_BLOCK,
+ VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
+ VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
+ VK_FORMAT_ASTC_12x10_UNORM_BLOCK,
+ VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
+ VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
+ VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
+ VK_FORMAT_ASTC_4x4_UNORM_BLOCK,
+ VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
+ VK_FORMAT_ASTC_5x4_UNORM_BLOCK,
+ VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
+ VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
+ VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
+ VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
+ VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_8x5_UNORM_BLOCK,
+ VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
+ VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
+ VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
+ VK_FORMAT_ASTC_8x8_UNORM_BLOCK,
+ VK_FORMAT_B10G11R11_UFLOAT_PACK32,
+ VK_FORMAT_B4G4R4A4_UNORM_PACK16,
+ VK_FORMAT_B5G5R5A1_UNORM_PACK16,
VK_FORMAT_B5G6R5_UNORM_PACK16,
- VK_FORMAT_A2B10G10R10_UNORM_PACK32,
- VK_FORMAT_A2B10G10R10_UINT_PACK32,
- VK_FORMAT_A1R5G5B5_UNORM_PACK16,
- VK_FORMAT_R32G32B32A32_SFLOAT,
- VK_FORMAT_R32G32B32A32_SINT,
- VK_FORMAT_R32G32B32A32_UINT,
- VK_FORMAT_R32G32_SFLOAT,
- VK_FORMAT_R32G32_SINT,
- VK_FORMAT_R32G32_UINT,
+ VK_FORMAT_B8G8R8A8_SRGB,
+ VK_FORMAT_B8G8R8A8_UNORM,
+ VK_FORMAT_BC1_RGBA_SRGB_BLOCK,
+ VK_FORMAT_BC1_RGBA_UNORM_BLOCK,
+ VK_FORMAT_BC2_SRGB_BLOCK,
+ VK_FORMAT_BC2_UNORM_BLOCK,
+ VK_FORMAT_BC3_SRGB_BLOCK,
+ VK_FORMAT_BC3_UNORM_BLOCK,
+ VK_FORMAT_BC4_SNORM_BLOCK,
+ VK_FORMAT_BC4_UNORM_BLOCK,
+ VK_FORMAT_BC5_SNORM_BLOCK,
+ VK_FORMAT_BC5_UNORM_BLOCK,
+ VK_FORMAT_BC6H_SFLOAT_BLOCK,
+ VK_FORMAT_BC6H_UFLOAT_BLOCK,
+ VK_FORMAT_BC7_SRGB_BLOCK,
+ VK_FORMAT_BC7_UNORM_BLOCK,
+ VK_FORMAT_D16_UNORM,
+ VK_FORMAT_D16_UNORM_S8_UINT,
+ VK_FORMAT_D24_UNORM_S8_UINT,
+ VK_FORMAT_D32_SFLOAT,
+ VK_FORMAT_D32_SFLOAT_S8_UINT,
+ VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,
+ VK_FORMAT_R16G16B16A16_SFLOAT,
VK_FORMAT_R16G16B16A16_SINT,
- VK_FORMAT_R16G16B16A16_UINT,
VK_FORMAT_R16G16B16A16_SNORM,
+ VK_FORMAT_R16G16B16A16_SSCALED,
+ VK_FORMAT_R16G16B16A16_UINT,
VK_FORMAT_R16G16B16A16_UNORM,
- VK_FORMAT_R16G16_UNORM,
- VK_FORMAT_R16G16_SNORM,
+ VK_FORMAT_R16G16B16A16_USCALED,
+ VK_FORMAT_R16G16B16_SFLOAT,
+ VK_FORMAT_R16G16B16_SINT,
+ VK_FORMAT_R16G16B16_SNORM,
+ VK_FORMAT_R16G16B16_SSCALED,
+ VK_FORMAT_R16G16B16_UINT,
+ VK_FORMAT_R16G16B16_UNORM,
+ VK_FORMAT_R16G16B16_USCALED,
VK_FORMAT_R16G16_SFLOAT,
- VK_FORMAT_R16G16_UINT,
VK_FORMAT_R16G16_SINT,
- VK_FORMAT_R16_UNORM,
+ VK_FORMAT_R16G16_SNORM,
+ VK_FORMAT_R16G16_SSCALED,
+ VK_FORMAT_R16G16_UINT,
+ VK_FORMAT_R16G16_UNORM,
+ VK_FORMAT_R16G16_USCALED,
+ VK_FORMAT_R16_SFLOAT,
+ VK_FORMAT_R16_SINT,
VK_FORMAT_R16_SNORM,
+ VK_FORMAT_R16_SSCALED,
VK_FORMAT_R16_UINT,
+ VK_FORMAT_R16_UNORM,
+ VK_FORMAT_R16_USCALED,
+ VK_FORMAT_R32G32B32A32_SFLOAT,
+ VK_FORMAT_R32G32B32A32_SINT,
+ VK_FORMAT_R32G32B32A32_UINT,
+ VK_FORMAT_R32G32B32_SFLOAT,
+ VK_FORMAT_R32G32B32_SINT,
+ VK_FORMAT_R32G32B32_UINT,
+ VK_FORMAT_R32G32_SFLOAT,
+ VK_FORMAT_R32G32_SINT,
+ VK_FORMAT_R32G32_UINT,
+ VK_FORMAT_R32_SFLOAT,
+ VK_FORMAT_R32_SINT,
+ VK_FORMAT_R32_UINT,
+ VK_FORMAT_R4G4B4A4_UNORM_PACK16,
+ VK_FORMAT_R4G4_UNORM_PACK8,
+ VK_FORMAT_R5G5B5A1_UNORM_PACK16,
+ VK_FORMAT_R5G6B5_UNORM_PACK16,
+ VK_FORMAT_R8G8B8A8_SINT,
+ VK_FORMAT_R8G8B8A8_SNORM,
VK_FORMAT_R8G8B8A8_SRGB,
- VK_FORMAT_R8G8_UNORM,
- VK_FORMAT_R8G8_SNORM,
+ VK_FORMAT_R8G8B8A8_SSCALED,
+ VK_FORMAT_R8G8B8A8_UINT,
+ VK_FORMAT_R8G8B8A8_UNORM,
+ VK_FORMAT_R8G8B8A8_USCALED,
+ VK_FORMAT_R8G8B8_SINT,
+ VK_FORMAT_R8G8B8_SNORM,
+ VK_FORMAT_R8G8B8_SSCALED,
+ VK_FORMAT_R8G8B8_UINT,
+ VK_FORMAT_R8G8B8_UNORM,
+ VK_FORMAT_R8G8B8_USCALED,
VK_FORMAT_R8G8_SINT,
+ VK_FORMAT_R8G8_SNORM,
+ VK_FORMAT_R8G8_SSCALED,
VK_FORMAT_R8G8_UINT,
- VK_FORMAT_R8_UNORM,
- VK_FORMAT_R8_SNORM,
+ VK_FORMAT_R8G8_UNORM,
+ VK_FORMAT_R8G8_USCALED,
VK_FORMAT_R8_SINT,
+ VK_FORMAT_R8_SNORM,
+ VK_FORMAT_R8_SSCALED,
VK_FORMAT_R8_UINT,
- VK_FORMAT_B10G11R11_UFLOAT_PACK32,
- VK_FORMAT_R32_SFLOAT,
- VK_FORMAT_R32_UINT,
- VK_FORMAT_R32_SINT,
- VK_FORMAT_R16_SFLOAT,
- VK_FORMAT_R16G16B16A16_SFLOAT,
- VK_FORMAT_B8G8R8A8_UNORM,
- VK_FORMAT_B8G8R8A8_SRGB,
- VK_FORMAT_R4G4B4A4_UNORM_PACK16,
- VK_FORMAT_D32_SFLOAT,
- VK_FORMAT_D16_UNORM,
+ VK_FORMAT_R8_UNORM,
+ VK_FORMAT_R8_USCALED,
VK_FORMAT_S8_UINT,
- VK_FORMAT_D16_UNORM_S8_UINT,
- VK_FORMAT_D24_UNORM_S8_UINT,
- VK_FORMAT_D32_SFLOAT_S8_UINT,
- VK_FORMAT_BC1_RGBA_UNORM_BLOCK,
- VK_FORMAT_BC2_UNORM_BLOCK,
- VK_FORMAT_BC3_UNORM_BLOCK,
- VK_FORMAT_BC4_UNORM_BLOCK,
- VK_FORMAT_BC4_SNORM_BLOCK,
- VK_FORMAT_BC5_UNORM_BLOCK,
- VK_FORMAT_BC5_SNORM_BLOCK,
- VK_FORMAT_BC7_UNORM_BLOCK,
- VK_FORMAT_BC6H_UFLOAT_BLOCK,
- VK_FORMAT_BC6H_SFLOAT_BLOCK,
- VK_FORMAT_BC1_RGBA_SRGB_BLOCK,
- VK_FORMAT_BC2_SRGB_BLOCK,
- VK_FORMAT_BC3_SRGB_BLOCK,
- VK_FORMAT_BC7_SRGB_BLOCK,
- VK_FORMAT_ASTC_4x4_UNORM_BLOCK,
- VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
- VK_FORMAT_ASTC_5x4_UNORM_BLOCK,
- VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
- VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
- VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
- VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
- VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
- VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
- VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
- VK_FORMAT_ASTC_8x5_UNORM_BLOCK,
- VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
- VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
- VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
- VK_FORMAT_ASTC_8x8_UNORM_BLOCK,
- VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
- VK_FORMAT_ASTC_10x5_UNORM_BLOCK,
- VK_FORMAT_ASTC_10x5_SRGB_BLOCK,
- VK_FORMAT_ASTC_10x6_UNORM_BLOCK,
- VK_FORMAT_ASTC_10x6_SRGB_BLOCK,
- VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
- VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
- VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
- VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
- VK_FORMAT_ASTC_12x10_UNORM_BLOCK,
- VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
- VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
- VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
- VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
- VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
- VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
- VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
- VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,
};
std::unordered_map<VkFormat, VkFormatProperties> format_properties;
for (const auto format : formats) {
@@ -224,9 +297,14 @@ std::vector<std::string> GetSupportedExtensions(vk::PhysicalDevice physical) {
return supported_extensions;
}
+bool IsExtensionSupported(std::span<const std::string> supported_extensions,
+ std::string_view extension) {
+ return std::ranges::find(supported_extensions, extension) != supported_extensions.end();
+}
+
NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical,
std::span<const std::string> exts) {
- if (std::ranges::find(exts, VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME) != exts.end()) {
+ if (IsExtensionSupported(exts, VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME)) {
VkPhysicalDeviceFragmentShadingRatePropertiesKHR shading_rate_props{};
shading_rate_props.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR;
@@ -239,7 +317,7 @@ NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical,
return NvidiaArchitecture::AmpereOrNewer;
}
}
- if (std::ranges::find(exts, VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME) != exts.end()) {
+ if (IsExtensionSupported(exts, VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME)) {
return NvidiaArchitecture::Turing;
}
return NvidiaArchitecture::VoltaOrOlder;
@@ -542,7 +620,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
}
VkPhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR workgroup_layout;
- if (khr_workgroup_memory_explicit_layout) {
+ if (khr_workgroup_memory_explicit_layout && is_shader_int16_supported) {
workgroup_layout = {
.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_FEATURES_KHR,
@@ -553,6 +631,11 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
.workgroupMemoryExplicitLayout16BitAccess = VK_TRUE,
};
SetNext(next, workgroup_layout);
+ } else if (khr_workgroup_memory_explicit_layout) {
+ // TODO(lat9nq): Find a proper fix for this
+ LOG_WARNING(Render_Vulkan, "Disabling VK_KHR_workgroup_memory_explicit_layout due to a "
+ "yuzu bug when host driver does not support 16-bit integers");
+ khr_workgroup_memory_explicit_layout = false;
}
VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR executable_properties;
@@ -585,6 +668,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();
@@ -603,8 +691,14 @@ 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;
+ }
}
- if (ext_extended_dynamic_state && driver_id == VK_DRIVER_ID_MESA_RADV) {
+ const bool is_radv = driver_id == VK_DRIVER_ID_MESA_RADV;
+ if (ext_extended_dynamic_state && is_radv) {
// Mask driver version variant
const u32 version = (properties.driverVersion << 3) >> 3;
if (version < VK_MAKE_API_VERSION(0, 21, 2, 0)) {
@@ -613,6 +707,17 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
ext_extended_dynamic_state = false;
}
}
+ if (ext_vertex_input_dynamic_state && is_radv) {
+ // TODO(ameerj): Blacklist only offending driver versions
+ // TODO(ameerj): Confirm if RDNA1 is affected
+ const bool is_rdna2 =
+ IsExtensionSupported(supported_extensions, VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME);
+ if (is_rdna2) {
+ LOG_WARNING(Render_Vulkan,
+ "RADV has broken VK_EXT_vertex_input_dynamic_state on RDNA2 hardware");
+ ext_vertex_input_dynamic_state = false;
+ }
+ }
sets_per_pool = 64;
const bool is_amd =
@@ -628,7 +733,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
has_broken_cube_compatibility = true;
}
}
- const bool is_amd_or_radv = is_amd || driver_id == VK_DRIVER_ID_MESA_RADV;
+ const bool is_amd_or_radv = is_amd || is_radv;
if (ext_sampler_filter_minmax && is_amd_or_radv) {
// Disable ext_sampler_filter_minmax on AMD GCN4 and lower as it is broken.
if (!is_float16_supported) {
@@ -639,6 +744,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
}
const bool is_intel_windows = driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS;
+ const bool is_intel_anv = driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA;
if (ext_vertex_input_dynamic_state && is_intel_windows) {
LOG_WARNING(Render_Vulkan, "Blacklisting Intel for VK_EXT_vertex_input_dynamic_state");
ext_vertex_input_dynamic_state = false;
@@ -652,6 +758,10 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
LOG_WARNING(Render_Vulkan, "Intel proprietary drivers do not support MSAA image blits");
cant_blit_msaa = true;
}
+ if (is_intel_anv) {
+ LOG_WARNING(Render_Vulkan, "ANV driver does not support native BGR format");
+ must_emulate_bgr565 = true;
+ }
supports_d24_depth =
IsFormatSupported(VK_FORMAT_D24_UNORM_S8_UINT,
@@ -671,9 +781,10 @@ VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags
// The wanted format is not supported by hardware, search for alternatives
const VkFormat* alternatives = GetFormatAlternatives(wanted_format);
if (alternatives == nullptr) {
- UNREACHABLE_MSG("Format={} with usage={} and type={} has no defined alternatives and host "
- "hardware does not support it",
- wanted_format, wanted_usage, format_type);
+ ASSERT_MSG(false,
+ "Format={} with usage={} and type={} has no defined alternatives and host "
+ "hardware does not support it",
+ wanted_format, wanted_usage, format_type);
return wanted_format;
}
@@ -682,21 +793,22 @@ VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags
if (!IsFormatSupported(alternative, wanted_usage, format_type)) {
continue;
}
- LOG_WARNING(Render_Vulkan,
- "Emulating format={} with alternative format={} with usage={} and type={}",
- wanted_format, alternative, wanted_usage, format_type);
+ LOG_DEBUG(Render_Vulkan,
+ "Emulating format={} with alternative format={} with usage={} and type={}",
+ wanted_format, alternative, wanted_usage, format_type);
return alternative;
}
// No alternatives found, panic
- UNREACHABLE_MSG("Format={} with usage={} and type={} is not supported by the host hardware and "
- "doesn't support any of the alternatives",
- wanted_format, wanted_usage, format_type);
+ ASSERT_MSG(false,
+ "Format={} with usage={} and type={} is not supported by the host hardware and "
+ "doesn't support any of the alternatives",
+ wanted_format, wanted_usage, format_type);
return wanted_format;
}
void Device::ReportLoss() const {
- LOG_CRITICAL(Render_Vulkan, "Device loss occured!");
+ 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});
@@ -957,6 +1069,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);
@@ -969,7 +1082,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) {
@@ -1239,15 +1352,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 37d140ebd..d7cc6c593 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -1,12 +1,10 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include <string>
-#include <string_view>
#include <unordered_map>
#include <vector>
@@ -342,6 +340,12 @@ public:
return device_access_memory;
}
+ bool CanReportMemoryUsage() const {
+ return ext_memory_budget;
+ }
+
+ u64 GetDeviceMemoryUsage() const;
+
u32 GetSetsPerPool() const {
return sets_per_pool;
}
@@ -354,6 +358,10 @@ public:
return cant_blit_msaa;
}
+ bool MustEmulateBGR565() const {
+ return must_emulate_bgr565;
+ }
+
private:
/// Checks if the physical device is suitable.
void CheckSuitability(bool requires_swapchain) const;
@@ -418,6 +426,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.
@@ -442,16 +453,19 @@ 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
bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
bool supports_d24_depth{}; ///< Supports D24 depth buffers.
bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting.
+ bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format.
// 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_instance.cpp b/src/video_core/vulkan_common/vulkan_instance.cpp
index bfd6e6add..a082e3059 100644
--- a/src/video_core/vulkan_common/vulkan_instance.cpp
+++ b/src/video_core/vulkan_common/vulkan_instance.cpp
@@ -1,12 +1,9 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include <algorithm>
#include <future>
#include <optional>
#include <span>
-#include <utility>
#include <vector>
#include "common/common_types.h"
diff --git a/src/video_core/vulkan_common/vulkan_instance.h b/src/video_core/vulkan_common/vulkan_instance.h
index e5e3a7144..40419d802 100644
--- a/src/video_core/vulkan_common/vulkan_instance.h
+++ b/src/video_core/vulkan_common/vulkan_instance.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/vulkan_common/vulkan_library.cpp b/src/video_core/vulkan_common/vulkan_library.cpp
index 22833fa56..4eb3913ee 100644
--- a/src/video_core/vulkan_common/vulkan_library.cpp
+++ b/src/video_core/vulkan_common/vulkan_library.cpp
@@ -1,17 +1,17 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include <cstdlib>
#include <string>
#include "common/dynamic_library.h"
#include "common/fs/path_util.h"
+#include "common/logging/log.h"
#include "video_core/vulkan_common/vulkan_library.h"
namespace Vulkan {
Common::DynamicLibrary OpenLibrary() {
+ LOG_DEBUG(Render_Vulkan, "Looking for a Vulkan library");
Common::DynamicLibrary library;
#ifdef __APPLE__
// Check if a path to a specific Vulkan library has been specified.
@@ -24,9 +24,11 @@ Common::DynamicLibrary OpenLibrary() {
}
#else
std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1);
+ LOG_DEBUG(Render_Vulkan, "Trying Vulkan library: {}", filename);
if (!library.Open(filename.c_str())) {
// Android devices may not have libvulkan.so.1, only libvulkan.so.
filename = Common::DynamicLibrary::GetVersionedFilename("vulkan");
+ LOG_DEBUG(Render_Vulkan, "Trying Vulkan library (second attempt): {}", filename);
void(library.Open(filename.c_str()));
}
#endif
diff --git a/src/video_core/vulkan_common/vulkan_library.h b/src/video_core/vulkan_common/vulkan_library.h
index 8b28b0e17..364ca979b 100644
--- a/src/video_core/vulkan_common/vulkan_library.h
+++ b/src/video_core/vulkan_common/vulkan_library.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index 300a61205..6442898bd 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <bit>
@@ -50,7 +49,7 @@ struct Range {
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
}
- UNREACHABLE_MSG("Invalid memory usage={}", usage);
+ ASSERT_MSG(false, "Invalid memory usage={}", usage);
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
@@ -227,7 +226,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} {}
@@ -326,7 +325,7 @@ VkMemoryPropertyFlags MemoryAllocator::MemoryPropertyFlags(u32 type_mask,
// Remove device local, if it's not supported by the requested resource
return MemoryPropertyFlags(type_mask, flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
}
- UNREACHABLE_MSG("No compatible memory types found");
+ ASSERT_MSG(false, "No compatible memory types found");
return 0;
}
@@ -350,7 +349,7 @@ bool IsHostVisible(MemoryUsage usage) noexcept {
case MemoryUsage::Download:
return true;
}
- UNREACHABLE_MSG("Invalid memory usage={}", usage);
+ ASSERT_MSG(false, "Invalid memory usage={}", usage);
return false;
}
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h
index 86e8ed119..a5bff03fe 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.h
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h
@@ -1,12 +1,10 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <span>
-#include <utility>
#include <vector>
#include "common/common_types.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
diff --git a/src/video_core/vulkan_common/vulkan_surface.cpp b/src/video_core/vulkan_common/vulkan_surface.cpp
index 3c3238f96..69f9c494b 100644
--- a/src/video_core/vulkan_common/vulkan_surface.cpp
+++ b/src/video_core/vulkan_common/vulkan_surface.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/frontend/emu_window.h"
diff --git a/src/video_core/vulkan_common/vulkan_surface.h b/src/video_core/vulkan_common/vulkan_surface.h
index 05a169e32..5725143e6 100644
--- a/src/video_core/vulkan_common/vulkan_surface.h
+++ b/src/video_core/vulkan_common/vulkan_surface.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index a9faa4807..2ad98dcfe 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -1,12 +1,9 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
-#include <exception>
#include <memory>
#include <optional>
-#include <string_view>
#include <utility>
#include <vector>
@@ -239,8 +236,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
}
@@ -328,6 +325,8 @@ const char* ToString(VkResult result) noexcept {
return "VK_PIPELINE_COMPILE_REQUIRED_EXT";
case VkResult::VK_RESULT_MAX_ENUM:
return "VK_RESULT_MAX_ENUM";
+ case VkResult::VK_ERROR_COMPRESSION_EXHAUSTED_EXT:
+ return "VK_ERROR_COMPRESSION_EXHAUSTED_EXT";
}
return "Unknown";
}
@@ -928,9 +927,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 b7ae01c6c..1b3f493bd 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -1,11 +1,9 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <exception>
-#include <iterator>
#include <limits>
#include <memory>
#include <optional>
@@ -173,6 +171,7 @@ struct InstanceDispatch {
PFN_vkGetPhysicalDeviceFeatures2KHR vkGetPhysicalDeviceFeatures2KHR{};
PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties{};
PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties{};
+ PFN_vkGetPhysicalDeviceMemoryProperties2 vkGetPhysicalDeviceMemoryProperties2{};
PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties{};
PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR{};
PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties{};
@@ -520,9 +519,7 @@ public:
dld{rhs.dld} {}
/// Assign an allocation transfering ownership from another allocation.
- /// Releases any previously held allocation.
PoolAllocations& operator=(PoolAllocations&& rhs) noexcept {
- Release();
allocations = std::move(rhs.allocations);
num = rhs.num;
device = rhs.device;
@@ -531,11 +528,6 @@ public:
return *this;
}
- /// Destroys any held allocation.
- ~PoolAllocations() {
- Release();
- }
-
/// Returns the number of allocations.
std::size_t size() const noexcept {
return num;
@@ -558,19 +550,6 @@ public:
}
private:
- /// Destroys the held allocations if they exist.
- void Release() noexcept {
- if (!allocations) {
- return;
- }
- const Span<AllocationType> span(allocations.get(), num);
- const VkResult result = Free(device, pool, span, *dld);
- // There's no way to report errors from a destructor.
- if (result != VK_SUCCESS) {
- std::terminate();
- }
- }
-
std::unique_ptr<AllocationType[]> allocations;
std::size_t num = 0;
VkDevice device = nullptr;
@@ -951,7 +930,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/CMakeLists.txt b/src/web_service/CMakeLists.txt
index ae85a72ea..3f75d97d1 100644
--- a/src/web_service/CMakeLists.txt
+++ b/src/web_service/CMakeLists.txt
@@ -1,12 +1,19 @@
+# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
add_library(web_service STATIC
+ announce_room_json.cpp
+ announce_room_json.h
telemetry_json.cpp
telemetry_json.h
verify_login.cpp
verify_login.h
+ verify_user_jwt.cpp
+ verify_user_jwt.h
web_backend.cpp
web_backend.h
web_result.h
)
create_target_directory_groups(web_service)
-target_link_libraries(web_service PRIVATE common nlohmann_json::nlohmann_json httplib)
+target_link_libraries(web_service PRIVATE common network nlohmann_json::nlohmann_json httplib cpp-jwt)
diff --git a/src/web_service/announce_room_json.cpp b/src/web_service/announce_room_json.cpp
new file mode 100644
index 000000000..4c3195efd
--- /dev/null
+++ b/src/web_service/announce_room_json.cpp
@@ -0,0 +1,145 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <future>
+#include <nlohmann/json.hpp>
+#include "common/detached_tasks.h"
+#include "common/logging/log.h"
+#include "web_service/announce_room_json.h"
+#include "web_service/web_backend.h"
+
+namespace AnnounceMultiplayerRoom {
+
+static void to_json(nlohmann::json& json, const Member& member) {
+ if (!member.username.empty()) {
+ json["username"] = member.username;
+ }
+ json["nickname"] = member.nickname;
+ if (!member.avatar_url.empty()) {
+ json["avatarUrl"] = member.avatar_url;
+ }
+ json["gameName"] = member.game.name;
+ json["gameId"] = member.game.id;
+}
+
+static void from_json(const nlohmann::json& json, Member& member) {
+ member.nickname = json.at("nickname").get<std::string>();
+ member.game.name = json.at("gameName").get<std::string>();
+ member.game.id = json.at("gameId").get<u64>();
+ try {
+ member.username = json.at("username").get<std::string>();
+ member.avatar_url = json.at("avatarUrl").get<std::string>();
+ } catch (const nlohmann::detail::out_of_range&) {
+ member.username = member.avatar_url = "";
+ LOG_DEBUG(Network, "Member \'{}\' isn't authenticated", member.nickname);
+ }
+}
+
+static void to_json(nlohmann::json& json, const Room& room) {
+ json["port"] = room.information.port;
+ json["name"] = room.information.name;
+ if (!room.information.description.empty()) {
+ json["description"] = room.information.description;
+ }
+ json["preferredGameName"] = room.information.preferred_game.name;
+ json["preferredGameId"] = room.information.preferred_game.id;
+ json["maxPlayers"] = room.information.member_slots;
+ json["netVersion"] = room.net_version;
+ json["hasPassword"] = room.has_password;
+ if (room.members.size() > 0) {
+ nlohmann::json member_json = room.members;
+ json["players"] = member_json;
+ }
+}
+
+static void from_json(const nlohmann::json& json, Room& room) {
+ room.verify_uid = json.at("externalGuid").get<std::string>();
+ room.ip = json.at("address").get<std::string>();
+ room.information.name = json.at("name").get<std::string>();
+ try {
+ room.information.description = json.at("description").get<std::string>();
+ } catch (const nlohmann::detail::out_of_range&) {
+ room.information.description = "";
+ LOG_DEBUG(Network, "Room \'{}\' doesn't contain a description", room.information.name);
+ }
+ room.information.host_username = json.at("owner").get<std::string>();
+ room.information.port = json.at("port").get<u16>();
+ room.information.preferred_game.name = json.at("preferredGameName").get<std::string>();
+ room.information.preferred_game.id = json.at("preferredGameId").get<u64>();
+ room.information.member_slots = json.at("maxPlayers").get<u32>();
+ room.net_version = json.at("netVersion").get<u32>();
+ room.has_password = json.at("hasPassword").get<bool>();
+ try {
+ room.members = json.at("players").get<std::vector<Member>>();
+ } catch (const nlohmann::detail::out_of_range& e) {
+ LOG_DEBUG(Network, "Out of range {}", e.what());
+ }
+}
+
+} // namespace AnnounceMultiplayerRoom
+
+namespace WebService {
+
+void RoomJson::SetRoomInformation(const std::string& name, const std::string& description,
+ const u16 port, const u32 max_player, const u32 net_version,
+ const bool has_password,
+ const AnnounceMultiplayerRoom::GameInfo& preferred_game) {
+ room.information.name = name;
+ room.information.description = description;
+ room.information.port = port;
+ room.information.member_slots = max_player;
+ room.net_version = net_version;
+ room.has_password = has_password;
+ room.information.preferred_game = preferred_game;
+}
+void RoomJson::AddPlayer(const AnnounceMultiplayerRoom::Member& member) {
+ room.members.push_back(member);
+}
+
+WebService::WebResult RoomJson::Update() {
+ if (room_id.empty()) {
+ LOG_ERROR(WebService, "Room must be registered to be updated");
+ return WebService::WebResult{WebService::WebResult::Code::LibError,
+ "Room is not registered", ""};
+ }
+ nlohmann::json json{{"players", room.members}};
+ return client.PostJson(fmt::format("/lobby/{}", room_id), json.dump(), false);
+}
+
+WebService::WebResult RoomJson::Register() {
+ nlohmann::json json = room;
+ auto result = client.PostJson("/lobby", json.dump(), false);
+ if (result.result_code != WebService::WebResult::Code::Success) {
+ return result;
+ }
+ auto reply_json = nlohmann::json::parse(result.returned_data);
+ room = reply_json.get<AnnounceMultiplayerRoom::Room>();
+ room_id = reply_json.at("id").get<std::string>();
+ return WebService::WebResult{WebService::WebResult::Code::Success, "", room.verify_uid};
+}
+
+void RoomJson::ClearPlayers() {
+ room.members.clear();
+}
+
+AnnounceMultiplayerRoom::RoomList RoomJson::GetRoomList() {
+ auto reply = client.GetJson("/lobby", true).returned_data;
+ if (reply.empty()) {
+ return {};
+ }
+ return nlohmann::json::parse(reply).at("rooms").get<AnnounceMultiplayerRoom::RoomList>();
+}
+
+void RoomJson::Delete() {
+ if (room_id.empty()) {
+ LOG_ERROR(WebService, "Room must be registered to be deleted");
+ return;
+ }
+ Common::DetachedTasks::AddTask(
+ [host{this->host}, username{this->username}, token{this->token}, room_id{this->room_id}]() {
+ // create a new client here because the this->client might be destroyed.
+ Client{host, username, token}.DeleteJson(fmt::format("/lobby/{}", room_id), "", false);
+ });
+}
+
+} // namespace WebService
diff --git a/src/web_service/announce_room_json.h b/src/web_service/announce_room_json.h
new file mode 100644
index 000000000..32c08858d
--- /dev/null
+++ b/src/web_service/announce_room_json.h
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <functional>
+#include <string>
+#include "common/announce_multiplayer_room.h"
+#include "web_service/web_backend.h"
+
+namespace WebService {
+
+/**
+ * Implementation of AnnounceMultiplayerRoom::Backend that (de)serializes room information into/from
+ * JSON, and submits/gets it to/from the yuzu web service
+ */
+class RoomJson : public AnnounceMultiplayerRoom::Backend {
+public:
+ RoomJson(const std::string& host_, const std::string& username_, const std::string& token_)
+ : client(host_, username_, token_), host(host_), username(username_), token(token_) {}
+ ~RoomJson() = default;
+ void SetRoomInformation(const std::string& name, const std::string& description, const u16 port,
+ const u32 max_player, const u32 net_version, const bool has_password,
+ const AnnounceMultiplayerRoom::GameInfo& preferred_game) override;
+ void AddPlayer(const AnnounceMultiplayerRoom::Member& member) override;
+ WebResult Update() override;
+ WebResult Register() override;
+ void ClearPlayers() override;
+ AnnounceMultiplayerRoom::RoomList GetRoomList() override;
+ void Delete() override;
+
+private:
+ AnnounceMultiplayerRoom::Room room;
+ Client client;
+ std::string host;
+ std::string username;
+ std::string token;
+ std::string room_id;
+};
+
+} // namespace WebService
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
index 6215c914f..51c792004 100644
--- a/src/web_service/telemetry_json.cpp
+++ b/src/web_service/telemetry_json.cpp
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <nlohmann/json.hpp>
#include "common/detached_tasks.h"
@@ -13,8 +12,8 @@ namespace WebService {
namespace Telemetry = Common::Telemetry;
struct TelemetryJson::Impl {
- Impl(std::string host, std::string username, std::string token)
- : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {}
+ Impl(std::string host_, std::string username_, std::string token_)
+ : host{std::move(host_)}, username{std::move(username_)}, token{std::move(token_)} {}
nlohmann::json& TopSection() {
return sections[static_cast<u8>(Telemetry::FieldType::None)];
diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h
index df51e00f8..504002c04 100644
--- a/src/web_service/telemetry_json.h
+++ b/src/web_service/telemetry_json.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp
index ceb55ca6b..050080278 100644
--- a/src/web_service/verify_login.cpp
+++ b/src/web_service/verify_login.cpp
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <nlohmann/json.hpp>
#include "web_service/verify_login.h"
diff --git a/src/web_service/verify_login.h b/src/web_service/verify_login.h
index 821b345d7..8d0adce74 100644
--- a/src/web_service/verify_login.h
+++ b/src/web_service/verify_login.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/web_service/verify_user_jwt.cpp b/src/web_service/verify_user_jwt.cpp
new file mode 100644
index 000000000..129eb1968
--- /dev/null
+++ b/src/web_service/verify_user_jwt.cpp
@@ -0,0 +1,69 @@
+// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
+#endif
+#include <jwt/jwt.hpp>
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+#include <system_error>
+#include "common/logging/log.h"
+#include "web_service/verify_user_jwt.h"
+#include "web_service/web_backend.h"
+#include "web_service/web_result.h"
+
+namespace WebService {
+
+static std::string public_key;
+std::string GetPublicKey(const std::string& host) {
+ if (public_key.empty()) {
+ Client client(host, "", ""); // no need for credentials here
+ public_key = client.GetPlain("/jwt/external/key.pem", true).returned_data;
+ if (public_key.empty()) {
+ LOG_ERROR(WebService, "Could not fetch external JWT public key, verification may fail");
+ } else {
+ LOG_INFO(WebService, "Fetched external JWT public key (size={})", public_key.size());
+ }
+ }
+ return public_key;
+}
+
+VerifyUserJWT::VerifyUserJWT(const std::string& host) : pub_key(GetPublicKey(host)) {}
+
+Network::VerifyUser::UserData VerifyUserJWT::LoadUserData(const std::string& verify_uid,
+ const std::string& token) {
+ const std::string audience = fmt::format("external-{}", verify_uid);
+ using namespace jwt::params;
+ std::error_code error;
+
+ // We use the Citra backend so the issuer is citra-core
+ auto decoded =
+ jwt::decode(token, algorithms({"rs256"}), error, secret(pub_key), issuer("citra-core"),
+ aud(audience), validate_iat(true), validate_jti(true));
+ if (error) {
+ LOG_INFO(WebService, "Verification failed: category={}, code={}, message={}",
+ error.category().name(), error.value(), error.message());
+ return {};
+ }
+ Network::VerifyUser::UserData user_data{};
+ if (decoded.payload().has_claim("username")) {
+ user_data.username = decoded.payload().get_claim_value<std::string>("username");
+ }
+ if (decoded.payload().has_claim("displayName")) {
+ user_data.display_name = decoded.payload().get_claim_value<std::string>("displayName");
+ }
+ if (decoded.payload().has_claim("avatarUrl")) {
+ user_data.avatar_url = decoded.payload().get_claim_value<std::string>("avatarUrl");
+ }
+ if (decoded.payload().has_claim("roles")) {
+ auto roles = decoded.payload().get_claim_value<std::vector<std::string>>("roles");
+ user_data.moderator = std::find(roles.begin(), roles.end(), "moderator") != roles.end();
+ }
+ return user_data;
+}
+
+} // namespace WebService
diff --git a/src/web_service/verify_user_jwt.h b/src/web_service/verify_user_jwt.h
new file mode 100644
index 000000000..27b0a100c
--- /dev/null
+++ b/src/web_service/verify_user_jwt.h
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <fmt/format.h>
+#include "network/verify_user.h"
+#include "web_service/web_backend.h"
+
+namespace WebService {
+
+std::string GetPublicKey(const std::string& host);
+
+class VerifyUserJWT final : public Network::VerifyUser::Backend {
+public:
+ VerifyUserJWT(const std::string& host);
+ ~VerifyUserJWT() = default;
+
+ Network::VerifyUser::UserData LoadUserData(const std::string& verify_uid,
+ const std::string& token) override;
+
+private:
+ std::string pub_key;
+};
+
+} // namespace WebService
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index b1e02c57a..12a7e4922 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -1,9 +1,7 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
-#include <cstdlib>
#include <mutex>
#include <string>
@@ -31,10 +29,10 @@ constexpr std::array<const char, 1> API_VERSION{'1'};
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};
- if (this->username == jwt_cache.username && this->token == jwt_cache.token) {
+ Impl(std::string host_, std::string username_, std::string token_)
+ : host{std::move(host_)}, username{std::move(username_)}, token{std::move(token_)} {
+ std::scoped_lock lock{jwt_cache.mutex};
+ if (username == jwt_cache.username && token == jwt_cache.token) {
jwt = jwt_cache.jwt;
}
}
@@ -70,8 +68,8 @@ struct Client::Impl {
*/
WebResult GenericRequest(const std::string& method, const std::string& path,
const std::string& data, const std::string& accept,
- const std::string& jwt = "", const std::string& username = "",
- const std::string& token = "") {
+ const std::string& jwt_ = "", const std::string& username_ = "",
+ const std::string& token_ = "") {
if (cli == nullptr) {
cli = std::make_unique<httplib::Client>(host.c_str());
}
@@ -86,14 +84,14 @@ struct Client::Impl {
cli->set_write_timeout(TIMEOUT_SECONDS);
httplib::Headers params;
- if (!jwt.empty()) {
+ if (!jwt_.empty()) {
params = {
- {std::string("Authorization"), fmt::format("Bearer {}", jwt)},
+ {std::string("Authorization"), fmt::format("Bearer {}", jwt_)},
};
- } else if (!username.empty()) {
+ } else if (!username_.empty()) {
params = {
- {std::string("x-username"), username},
- {std::string("x-token"), token},
+ {std::string("x-username"), username_},
+ {std::string("x-token"), token_},
};
}
@@ -113,7 +111,8 @@ struct Client::Impl {
httplib::Error error;
if (!cli->send(request, response, error)) {
- LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
+ LOG_ERROR(WebService, "{} to {} returned null (httplib Error: {})", method, host + path,
+ httplib::to_string(error));
return WebResult{WebResult::Code::LibError, "Null response", ""};
}
@@ -148,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/web_service/web_backend.h b/src/web_service/web_backend.h
index 81f58583c..11b5f558c 100644
--- a/src/web_service/web_backend.h
+++ b/src/web_service/web_backend.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/web_service/web_result.h b/src/web_service/web_result.h
index 3aeeb5288..6da3a9277 100644
--- a/src/web_service/web_result.h
+++ b/src/web_service/web_result.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 30902101d..29d506c47 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
@@ -41,6 +44,9 @@ add_executable(yuzu
configuration/configure_audio.cpp
configuration/configure_audio.h
configuration/configure_audio.ui
+ configuration/configure_camera.cpp
+ configuration/configure_camera.h
+ configuration/configure_camera.ui
configuration/configure_cpu.cpp
configuration/configure_cpu.h
configuration/configure_cpu.ui
@@ -99,6 +105,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
@@ -150,8 +159,36 @@ add_executable(yuzu
main.cpp
main.h
main.ui
+ multiplayer/chat_room.cpp
+ multiplayer/chat_room.h
+ multiplayer/chat_room.ui
+ multiplayer/client_room.h
+ multiplayer/client_room.cpp
+ multiplayer/client_room.ui
+ multiplayer/direct_connect.cpp
+ multiplayer/direct_connect.h
+ multiplayer/direct_connect.ui
+ multiplayer/host_room.cpp
+ multiplayer/host_room.h
+ multiplayer/host_room.ui
+ multiplayer/lobby.cpp
+ multiplayer/lobby.h
+ multiplayer/lobby.ui
+ multiplayer/lobby_p.h
+ multiplayer/message.cpp
+ multiplayer/message.h
+ multiplayer/moderation_dialog.cpp
+ multiplayer/moderation_dialog.h
+ multiplayer/moderation_dialog.ui
+ multiplayer/state.cpp
+ multiplayer/state.h
+ multiplayer/validation.h
+ startup_checks.cpp
+ startup_checks.h
uisettings.cpp
uisettings.h
+ util/clickable_label.cpp
+ util/clickable_label.h
util/controller_navigation.cpp
util/controller_navigation.h
util/limitable_input_dialog.cpp
@@ -171,6 +208,16 @@ add_executable(yuzu
yuzu.rc
)
+if (WIN32 AND YUZU_CRASH_DUMPS)
+ target_sources(yuzu PRIVATE
+ mini_dump.cpp
+ mini_dump.h
+ )
+
+ target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY})
+ target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP)
+endif()
+
file(GLOB COMPAT_LIST
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
@@ -184,7 +231,10 @@ if (ENABLE_QT_TRANSLATION)
# Update source TS file if enabled
if (GENERATE_QT_TRANSLATION)
get_target_property(SRCS yuzu SOURCES)
- qt5_create_translation(QM_FILES
+ # these calls to qt_create_translation also creates a rule to generate en.qm which conflicts with providing english plurals
+ # so we have to set a OUTPUT_LOCATION so that we don't have multiple rules to generate en.qm
+ set_source_files_properties(${YUZU_QT_LANGUAGES}/en.ts PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/translations")
+ qt_create_translation(QM_FILES
${SRCS}
${UIS}
${YUZU_QT_LANGUAGES}/en.ts
@@ -192,7 +242,13 @@ if (ENABLE_QT_TRANSLATION)
-source-language en_US
-target-language en_US
)
- add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts)
+
+ # Generate plurals into dist/english_plurals/generated_en.ts so it can be used to revise dist/english_plurals/en.ts
+ set(GENERATED_PLURALS_FILE ${PROJECT_SOURCE_DIR}/dist/english_plurals/generated_en.ts)
+ set_source_files_properties(${GENERATED_PLURALS_FILE} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/plurals")
+ qt_create_translation(QM_FILES ${SRCS} ${UIS} ${GENERATED_PLURALS_FILE} OPTIONS -pluralonly -source-language en_US -target-language en_US)
+
+ add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts ${GENERATED_PLURALS_FILE})
endif()
# Find all TS files except en.ts
@@ -200,7 +256,10 @@ if (ENABLE_QT_TRANSLATION)
list(REMOVE_ITEM LANGUAGES_TS ${YUZU_QT_LANGUAGES}/en.ts)
# Compile TS files to QM files
- qt5_add_translation(LANGUAGES_QM ${LANGUAGES_TS})
+ qt_add_translation(LANGUAGES_QM ${LANGUAGES_TS})
+
+ # Compile english plurals TS file to en.qm
+ qt_add_translation(LANGUAGES_QM ${PROJECT_SOURCE_DIR}/dist/english_plurals/en.ts)
# Build a QRC file from the QM file list
set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc)
@@ -212,7 +271,7 @@ if (ENABLE_QT_TRANSLATION)
file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>")
# Add the QRC file to package in all QM files
- qt5_add_resources(LANGUAGES ${LANGUAGES_QRC})
+ qt_add_resources(LANGUAGES ${LANGUAGES_QRC})
else()
set(LANGUAGES)
endif()
@@ -233,18 +292,23 @@ if (APPLE)
set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
elseif(WIN32)
# compile as a win32 gui application instead of a console application
- target_link_libraries(yuzu PRIVATE Qt5::WinMain)
+ if (QT_VERSION VERSION_GREATER 6)
+ target_link_libraries(yuzu PRIVATE Qt6::EntryPointPrivate)
+ else()
+ target_link_libraries(yuzu PRIVATE Qt5::WinMain)
+ endif()
if(MSVC)
+ target_link_libraries(yuzu PRIVATE version.lib)
set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
elseif(MINGW)
- set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "-mwindows")
+ set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "-Wl,--subsystem,windows")
endif()
endif()
create_target_directory_groups(yuzu)
-target_link_libraries(yuzu PRIVATE common core input_common video_core)
-target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::Widgets)
+target_link_libraries(yuzu PRIVATE common core input_common network video_core)
+target_link_libraries(yuzu PRIVATE Boost::boost glad Qt::Widgets Qt::Multimedia)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include)
@@ -252,7 +316,7 @@ if (NOT WIN32)
target_include_directories(yuzu PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
- target_link_libraries(yuzu PRIVATE Qt5::DBus)
+ target_link_libraries(yuzu PRIVATE Qt::DBus)
endif()
target_compile_definitions(yuzu PRIVATE
@@ -287,13 +351,17 @@ if (USE_DISCORD_PRESENCE)
target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
endif()
+if (ENABLE_WEB_SERVICE)
+ target_compile_definitions(yuzu PRIVATE -DENABLE_WEB_SERVICE)
+endif()
+
if (YUZU_USE_QT_WEB_ENGINE)
- target_link_libraries(yuzu PRIVATE Qt5::WebEngineCore Qt5::WebEngineWidgets)
+ target_link_libraries(yuzu PRIVATE Qt::WebEngineCore Qt::WebEngineWidgets)
target_compile_definitions(yuzu PRIVATE -DYUZU_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)
@@ -316,3 +384,7 @@ endif()
if (NOT APPLE)
target_compile_definitions(yuzu PRIVATE HAS_OPENGL)
endif()
+
+if (ARCHITECTURE_x86_64)
+ target_link_libraries(yuzu PRIVATE dynarmic)
+endif()
diff --git a/src/yuzu/Info.plist b/src/yuzu/Info.plist
index 5f1c95d54..0eb377926 100644
--- a/src/yuzu/Info.plist
+++ b/src/yuzu/Info.plist
@@ -1,4 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+SPDX-FileCopyrightText: 2015 Pierre de La Morinerie <kemenaran@gmail.com>
+SPDX-License-Identifier: GPL-2.0-or-later
+-->
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp
index 04ab4ae21..eeff54359 100644
--- a/src/yuzu/about_dialog.cpp
+++ b/src/yuzu/about_dialog.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QIcon>
#include <fmt/format.h>
@@ -20,7 +19,11 @@ AboutDialog::AboutDialog(QWidget* parent)
const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;
ui->setupUi(this);
- ui->labelLogo->setPixmap(QIcon::fromTheme(QStringLiteral("yuzu")).pixmap(200));
+ // Try and request the icon from Qt theme (Linux?)
+ const QIcon yuzu_logo = QIcon::fromTheme(QStringLiteral("org.yuzu_emu.yuzu"));
+ if (!yuzu_logo.isNull()) {
+ ui->labelLogo->setPixmap(yuzu_logo.pixmap(200));
+ }
ui->labelBuildInfo->setText(
ui->labelBuildInfo->text().arg(QString::fromStdString(yuzu_build_version),
QString::fromUtf8(Common::g_build_date).left(10)));
diff --git a/src/yuzu/about_dialog.h b/src/yuzu/about_dialog.h
index 18e8c11a7..3c4e71ee6 100644
--- a/src/yuzu/about_dialog.h
+++ b/src/yuzu/about_dialog.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/aboutdialog.ui b/src/yuzu/aboutdialog.ui
index 27d81cd13..aea82809d 100644
--- a/src/yuzu/aboutdialog.ui
+++ b/src/yuzu/aboutdialog.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>616</width>
- <height>261</height>
+ <height>294</height>
</rect>
</property>
<property name="windowTitle">
@@ -26,8 +26,20 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
+ <property name="maximumSize">
+ <size>
+ <width>200</width>
+ <height>200</height>
+ </size>
+ </property>
<property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../dist/qt_themes/default/default.qrc">:/icons/default/256x256/yuzu.png</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
</property>
</widget>
</item>
@@ -87,7 +99,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>
@@ -115,7 +127,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QLabel" name="labelLinks">
<property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/LICENSE.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
@@ -152,7 +164,8 @@ p, li { white-space: pre-wrap; }
</layout>
</widget>
<resources>
- <include location="../../dist/icons/icons.qrc"/>
+ <include location="../../dist/qt_themes_default/default/default.qrc"/>
+ <include location="../../dist/qt_themes/default/default.qrc"/>
</resources>
<connections>
<connection>
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index 4104928d1..12efdc216 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -1,12 +1,10 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <thread>
#include "common/assert.h"
-#include "common/param_package.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/hid/emulated_controller.h"
@@ -65,7 +63,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
InputCommon::InputSubsystem* input_subsystem_, Core::System& system_)
: QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
- input_profiles(std::make_unique<InputProfiles>(system_)), system{system_} {
+ input_profiles(std::make_unique<InputProfiles>()), system{system_} {
ui->setupUi(this);
player_widgets = {
@@ -293,7 +291,7 @@ bool QtControllerSelectorDialog::CheckIfParametersMet() {
// Here, we check and validate the current configuration against all applicable parameters.
const auto num_connected_players = static_cast<int>(
std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
- [this](const QGroupBox* player) { return player->isChecked(); }));
+ [](const QGroupBox* player) { return player->isChecked(); }));
const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
@@ -633,7 +631,7 @@ void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
switch (max_supported_players) {
case 0:
default:
- UNREACHABLE();
+ ASSERT(false);
return;
case 1:
ui->widgetSpacer->hide();
diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h
index 7ab9ced3d..cf948d2b5 100644
--- a/src/yuzu/applets/qt_controller.h
+++ b/src/yuzu/applets/qt_controller.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/applets/qt_error.cpp b/src/yuzu/applets/qt_error.cpp
index 879e73660..367d5352d 100644
--- a/src/yuzu/applets/qt_error.cpp
+++ b/src/yuzu/applets/qt_error.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QDateTime>
#include "yuzu/applets/qt_error.h"
@@ -15,7 +14,7 @@ QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) {
QtErrorDisplay::~QtErrorDisplay() = default;
-void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished) const {
+void QtErrorDisplay::ShowError(Result error, std::function<void()> finished) const {
callback = std::move(finished);
emit MainWindowDisplayError(
tr("Error Code: %1-%2 (0x%3)")
@@ -25,7 +24,7 @@ void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished)
tr("An error has occurred.\nPlease try again or contact the developer of the software."));
}
-void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
+void QtErrorDisplay::ShowErrorWithTimestamp(Result error, std::chrono::seconds time,
std::function<void()> finished) const {
callback = std::move(finished);
@@ -41,7 +40,7 @@ void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::secon
.arg(date_time.toString(QStringLiteral("h:mm:ss A"))));
}
-void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text,
+void QtErrorDisplay::ShowCustomErrorText(Result error, std::string dialog_text,
std::string fullscreen_text,
std::function<void()> finished) const {
callback = std::move(finished);
diff --git a/src/yuzu/applets/qt_error.h b/src/yuzu/applets/qt_error.h
index 8bd895a32..eb4107c7e 100644
--- a/src/yuzu/applets/qt_error.h
+++ b/src/yuzu/applets/qt_error.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -17,10 +16,10 @@ public:
explicit QtErrorDisplay(GMainWindow& parent);
~QtErrorDisplay() override;
- void ShowError(ResultCode error, std::function<void()> finished) const override;
- void ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
+ void ShowError(Result error, std::function<void()> finished) const override;
+ void ShowErrorWithTimestamp(Result error, std::chrono::seconds time,
std::function<void()> finished) const override;
- void ShowCustomErrorText(ResultCode error, std::string dialog_text, std::string fullscreen_text,
+ void ShowCustomErrorText(Result error, std::string dialog_text, std::string fullscreen_text,
std::function<void()> finished) const override;
signals:
diff --git a/src/yuzu/applets/qt_profile_select.cpp b/src/yuzu/applets/qt_profile_select.cpp
index 4cd8f7784..c8bcfb223 100644
--- a/src/yuzu/applets/qt_profile_select.cpp
+++ b/src/yuzu/applets/qt_profile_select.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include <QApplication>
@@ -10,6 +9,7 @@
#include <QLineEdit>
#include <QScrollArea>
#include <QStandardItemModel>
+#include <QTreeView>
#include <QVBoxLayout>
#include "common/fs/path_util.h"
#include "common/string_util.h"
@@ -100,6 +100,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(Core::HID::HIDCore& hid_core,
}
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
QCoreApplication::postEvent(tree_view, event);
+ SelectUser(tree_view->currentIndex());
});
const auto& profiles = profile_manager->GetAllUsers();
diff --git a/src/yuzu/applets/qt_profile_select.h b/src/yuzu/applets/qt_profile_select.h
index 56496ed31..124f2cdbd 100644
--- a/src/yuzu/applets/qt_profile_select.h
+++ b/src/yuzu/applets/qt_profile_select.h
@@ -1,13 +1,11 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <vector>
#include <QDialog>
#include <QList>
-#include <QTreeView>
#include "core/frontend/applets/profile_select.h"
#include "core/hle/service/acc/profile_manager.h"
@@ -19,6 +17,7 @@ class QLabel;
class QScrollArea;
class QStandardItem;
class QStandardItemModel;
+class QTreeView;
class QVBoxLayout;
namespace Core::HID {
diff --git a/src/yuzu/applets/qt_software_keyboard.cpp b/src/yuzu/applets/qt_software_keyboard.cpp
index c3857fc98..e60506197 100644
--- a/src/yuzu/applets/qt_software_keyboard.cpp
+++ b/src/yuzu/applets/qt_software_keyboard.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QCursor>
#include <QKeyEvent>
@@ -214,9 +213,9 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(
ui->button_ok_num,
},
{
- nullptr,
+ ui->button_left_optional_num,
ui->button_0_num,
- nullptr,
+ ui->button_right_optional_num,
ui->button_ok_num,
},
}};
@@ -331,7 +330,9 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(
ui->button_7_num,
ui->button_8_num,
ui->button_9_num,
+ ui->button_left_optional_num,
ui->button_0_num,
+ ui->button_right_optional_num,
};
SetupMouseHover();
@@ -343,6 +344,9 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(
ui->label_header->setText(QString::fromStdU16String(initialize_parameters.header_text));
ui->label_sub->setText(QString::fromStdU16String(initialize_parameters.sub_text));
+ ui->button_left_optional_num->setText(QChar{initialize_parameters.left_optional_symbol_key});
+ ui->button_right_optional_num->setText(QChar{initialize_parameters.right_optional_symbol_key});
+
current_text = initialize_parameters.initial_text;
cursor_position = initialize_parameters.initial_cursor_position;
@@ -412,11 +416,11 @@ void QtSoftwareKeyboardDialog::ShowTextCheckDialog(
break;
}
- auto text = ui->topOSK->currentIndex() == 1
- ? ui->text_edit_osk->toPlainText().toStdU16String()
- : ui->line_edit_osk->text().toStdU16String();
+ const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
+ : ui->line_edit_osk->text();
+ auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size());
- emit SubmitNormalText(SwkbdResult::Ok, std::move(text), true);
+ emit SubmitNormalText(SwkbdResult::Ok, std::move(text_str), true);
break;
}
}
@@ -563,7 +567,7 @@ void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) {
return;
}
- InlineTextInsertString(entered_text.toStdU16String());
+ InlineTextInsertString(Common::U16StringFromBuffer(entered_text.utf16(), entered_text.size()));
}
void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) {
@@ -933,6 +937,15 @@ void QtSoftwareKeyboardDialog::DisableKeyboardButtons() {
button->setEnabled(true);
}
}
+
+ const auto enable_left_optional = initialize_parameters.left_optional_symbol_key != '\0';
+ const auto enable_right_optional = initialize_parameters.right_optional_symbol_key != '\0';
+
+ ui->button_left_optional_num->setEnabled(enable_left_optional);
+ ui->button_left_optional_num->setVisible(enable_left_optional);
+
+ ui->button_right_optional_num->setEnabled(enable_right_optional);
+ ui->button_right_optional_num->setVisible(enable_right_optional);
break;
}
}
@@ -1020,7 +1033,10 @@ bool QtSoftwareKeyboardDialog::ValidateInputText(const QString& input_text) {
}
if (bottom_osk_index == BottomOSKIndex::NumberPad &&
- std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return !c.isDigit(); })) {
+ std::any_of(input_text.begin(), input_text.end(), [this](QChar c) {
+ return !c.isDigit() && c != QChar{initialize_parameters.left_optional_symbol_key} &&
+ c != QChar{initialize_parameters.right_optional_symbol_key};
+ })) {
return false;
}
@@ -1120,11 +1136,11 @@ void QtSoftwareKeyboardDialog::NormalKeyboardButtonClicked(QPushButton* button)
}
if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) {
- auto text = ui->topOSK->currentIndex() == 1
- ? ui->text_edit_osk->toPlainText().toStdU16String()
- : ui->line_edit_osk->text().toStdU16String();
+ const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
+ : ui->line_edit_osk->text();
+ auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size());
- emit SubmitNormalText(SwkbdResult::Ok, std::move(text));
+ emit SubmitNormalText(SwkbdResult::Ok, std::move(text_str));
return;
}
@@ -1190,7 +1206,8 @@ void QtSoftwareKeyboardDialog::InlineKeyboardButtonClicked(QPushButton* button)
return;
}
- InlineTextInsertString(button->text().toStdU16String());
+ const auto button_text = button->text();
+ InlineTextInsertString(Common::U16StringFromBuffer(button_text.utf16(), button_text.size()));
// Revert the keyboard to lowercase if the shift key is active.
if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) {
@@ -1283,11 +1300,11 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(Core::HID::NpadButton button
if (is_inline) {
emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position);
} else {
- auto text = ui->topOSK->currentIndex() == 1
- ? ui->text_edit_osk->toPlainText().toStdU16String()
- : ui->line_edit_osk->text().toStdU16String();
+ const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
+ : ui->line_edit_osk->text();
+ auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size());
- emit SubmitNormalText(SwkbdResult::Cancel, std::move(text));
+ emit SubmitNormalText(SwkbdResult::Cancel, std::move(text_str));
}
break;
case Core::HID::NpadButton::Y:
@@ -1384,6 +1401,10 @@ void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) {
}
};
+ // Store the initial row and column.
+ const auto initial_row = row;
+ const auto initial_column = column;
+
switch (bottom_osk_index) {
case BottomOSKIndex::LowerCase:
case BottomOSKIndex::UpperCase: {
@@ -1394,6 +1415,11 @@ void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) {
auto* curr_button = keyboard_buttons[index][row][column];
while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) {
+ // If we returned back to where we started from, break the loop.
+ if (row == initial_row && column == initial_column) {
+ break;
+ }
+
move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL);
curr_button = keyboard_buttons[index][row][column];
}
@@ -1408,6 +1434,11 @@ void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) {
auto* curr_button = numberpad_buttons[row][column];
while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) {
+ // If we returned back to where we started from, break the loop.
+ if (row == initial_row && column == initial_column) {
+ break;
+ }
+
move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD);
curr_button = numberpad_buttons[row][column];
}
diff --git a/src/yuzu/applets/qt_software_keyboard.h b/src/yuzu/applets/qt_software_keyboard.h
index b030cdcf7..35d4ee2ef 100644
--- a/src/yuzu/applets/qt_software_keyboard.h
+++ b/src/yuzu/applets/qt_software_keyboard.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -212,7 +211,7 @@ private:
std::array<std::array<QPushButton*, NUM_COLUMNS_NUMPAD>, NUM_ROWS_NUMPAD> numberpad_buttons;
// Contains a set of all buttons used in keyboard_buttons and numberpad_buttons.
- std::array<QPushButton*, 110> all_buttons;
+ std::array<QPushButton*, 112> all_buttons;
std::size_t row{0};
std::size_t column{0};
diff --git a/src/yuzu/applets/qt_software_keyboard.ui b/src/yuzu/applets/qt_software_keyboard.ui
index b0a1fcde9..9661cb260 100644
--- a/src/yuzu/applets/qt_software_keyboard.ui
+++ b/src/yuzu/applets/qt_software_keyboard.ui
@@ -3298,6 +3298,24 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
+ <item row="4" column="2">
+ <widget class="QPushButton" name="button_left_optional_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true"></string>
+ </property>
+ </widget>
+ </item>
<item row="4" column="3">
<widget class="QPushButton" name="button_0_num">
<property name="sizePolicy">
@@ -3316,6 +3334,24 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
+ <item row="4" column="4">
+ <widget class="QPushButton" name="button_right_optional_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true"></string>
+ </property>
+ </widget>
+ </item>
<item row="1" column="4">
<widget class="QPushButton" name="button_3_num">
<property name="sizePolicy">
@@ -3494,7 +3530,9 @@ p, li { white-space: pre-wrap; }
<tabstop>button_7_num</tabstop>
<tabstop>button_8_num</tabstop>
<tabstop>button_9_num</tabstop>
+ <tabstop>button_left_optional_num</tabstop>
<tabstop>button_0_num</tabstop>
+ <tabstop>button_right_optional_num</tabstop>
</tabstops>
<resources>
<include location="../../../dist/icons/overlay/overlay.qrc"/>
diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp
index cb3c5d826..89bd482e0 100644
--- a/src/yuzu/applets/qt_web_browser.cpp
+++ b/src/yuzu/applets/qt_web_browser.cpp
@@ -1,8 +1,9 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef YUZU_USE_QT_WEB_ENGINE
+#include <bit>
+
#include <QApplication>
#include <QKeyEvent>
@@ -11,17 +12,15 @@
#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"
-#include "common/param_package.h"
#include "core/core.h"
-#include "core/hid/hid_types.h"
-#include "core/hid/input_interpreter.h"
#include "input_common/drivers/keyboard.h"
-#include "input_common/main.h"
#include "yuzu/applets/qt_web_browser.h"
-#include "yuzu/applets/qt_web_browser_scripts.h"
#include "yuzu/main.h"
#include "yuzu/util/url_request_interceptor.h"
@@ -55,8 +54,8 @@ QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system,
: QWebEngineView(parent), input_subsystem{input_subsystem_},
url_interceptor(std::make_unique<UrlRequestInterceptor>()),
input_interpreter(std::make_unique<InputInterpreter>(system)),
- default_profile{QWebEngineProfile::defaultProfile()},
- global_settings{QWebEngineSettings::globalSettings()} {
+ default_profile{QWebEngineProfile::defaultProfile()}, global_settings{
+ default_profile->settings()} {
default_profile->setPersistentStoragePath(QString::fromStdString(Common::FS::PathToUTF8String(
Common::FS::GetYuzuPath(Common::FS::YuzuPath::YuzuDir) / "qtwebengine")));
@@ -81,7 +80,7 @@ QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system,
default_profile->scripts()->insert(gamepad);
default_profile->scripts()->insert(window_nx);
- default_profile->setRequestInterceptor(url_interceptor.get());
+ default_profile->setUrlRequestInterceptor(url_interceptor.get());
global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
@@ -214,8 +213,10 @@ template <Core::HID::NpadButton... T>
void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() {
const auto f = [this](Core::HID::NpadButton button) {
if (input_interpreter->IsButtonPressedOnce(button)) {
+ const auto button_index = std::countr_zero(static_cast<u64>(button));
+
page()->runJavaScript(
- QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast<u8>(button)),
+ QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(button_index),
[this, button](const QVariant& variant) {
if (variant.toBool()) {
switch (button) {
@@ -239,7 +240,7 @@ void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() {
page()->runJavaScript(
QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }")
- .arg(static_cast<u8>(button)));
+ .arg(button_index));
}
};
diff --git a/src/yuzu/applets/qt_web_browser.h b/src/yuzu/applets/qt_web_browser.h
index fa18aecac..043800853 100644
--- a/src/yuzu/applets/qt_web_browser.h
+++ b/src/yuzu/applets/qt_web_browser.h
@@ -1,11 +1,9 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
-#include <memory>
#include <thread>
#include <QObject>
diff --git a/src/yuzu/applets/qt_web_browser_scripts.h b/src/yuzu/applets/qt_web_browser_scripts.h
index c4ba8d40f..f5530c38f 100644
--- a/src/yuzu/applets/qt_web_browser_scripts.h
+++ b/src/yuzu/applets/qt_web_browser_scripts.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 114f17c06..24251247d 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -1,12 +1,12 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <glad/glad.h>
#include <QApplication>
+#include <QCameraImageCapture>
+#include <QCameraInfo>
#include <QHBoxLayout>
-#include <QKeyEvent>
#include <QMessageBox>
#include <QPainter>
#include <QScreen>
@@ -28,17 +28,17 @@
#include "common/assert.h"
#include "common/microprofile.h"
#include "common/scm_rev.h"
-#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/core.h"
+#include "core/cpu_manager.h"
#include "core/frontend/framebuffer_layout.h"
+#include "input_common/drivers/camera.h"
#include "input_common/drivers/keyboard.h"
#include "input_common/drivers/mouse.h"
#include "input_common/drivers/tas_input.h"
#include "input_common/drivers/touch_screen.h"
#include "input_common/main.h"
#include "video_core/renderer_base.h"
-#include "video_core/video_core.h"
#include "yuzu/bootmanager.h"
#include "yuzu/main.h"
@@ -47,12 +47,13 @@ EmuThread::EmuThread(Core::System& system_) : system{system_} {}
EmuThread::~EmuThread() = default;
void EmuThread::run() {
- std::string name = "yuzu:EmuControlThread";
+ std::string name = "EmuControlThread";
MicroProfileOnThreadCreate(name.c_str());
Common::SetCurrentThreadName(name.c_str());
auto& gpu = system.GPU();
auto stop_token = stop_source.get_token();
+ bool debugger_should_start = system.DebuggerEnabled();
system.RegisterHostThread();
@@ -75,6 +76,8 @@ void EmuThread::run() {
gpu.ReleaseContext();
+ system.GetCpuManager().OnGpuReady();
+
// Holds whether the cpu was running during the last iteration,
// so that the DebugModeLeft signal can be emitted before the
// next execution step
@@ -92,6 +95,12 @@ void EmuThread::run() {
this->SetRunning(false);
emit ErrorThrown(result, system.GetStatusDetails());
}
+
+ if (debugger_should_start) {
+ system.InitializeDebugger();
+ debugger_should_start = false;
+ }
+
running_wait.Wait();
result = system.Pause();
if (result != Core::SystemResultStatus::Success) {
@@ -105,11 +114,9 @@ void EmuThread::run() {
was_active = true;
emit DebugModeEntered();
}
- } else if (exec_step) {
- UNIMPLEMENTED();
} else {
std::unique_lock lock{running_mutex};
- running_cv.wait(lock, stop_token, [this] { return IsRunning() || exec_step; });
+ running_cv.wait(lock, stop_token, [this] { return IsRunning(); });
}
}
@@ -125,7 +132,7 @@ void EmuThread::run() {
class OpenGLSharedContext : public Core::Frontend::GraphicsContext {
public:
/// Create the original context that should be shared from
- explicit OpenGLSharedContext(QSurface* surface) : surface(surface) {
+ explicit OpenGLSharedContext(QSurface* surface_) : surface{surface_} {
QSurfaceFormat format;
format.setVersion(4, 6);
format.setProfile(QSurfaceFormat::CompatibilityProfile);
@@ -362,9 +369,9 @@ void GRenderWindow::RestoreGeometry() {
QWidget::restoreGeometry(geometry);
}
-void GRenderWindow::restoreGeometry(const QByteArray& geometry) {
+void GRenderWindow::restoreGeometry(const QByteArray& geometry_) {
// Make sure users of this class don't need to deal with backing up the geometry themselves
- QWidget::restoreGeometry(geometry);
+ QWidget::restoreGeometry(geometry_);
BackupGeometry();
}
@@ -750,7 +757,7 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
input_subsystem->GetMouse()->MouseMove(x, y, touch_x, touch_y, center_x, center_y);
if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
- QCursor::setPos(mapToGlobal({center_x, center_y}));
+ QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
}
emit MouseActivity();
@@ -775,65 +782,123 @@ void GRenderWindow::wheelEvent(QWheelEvent* event) {
void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
QList<QTouchEvent::TouchPoint> touch_points = event->touchPoints();
for (const auto& touch_point : touch_points) {
- if (!TouchUpdate(touch_point)) {
- TouchStart(touch_point);
- }
+ const auto [x, y] = ScaleTouch(touch_point.pos());
+ const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
+ input_subsystem->GetTouchScreen()->TouchPressed(touch_x, touch_y, touch_point.id());
}
}
void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
QList<QTouchEvent::TouchPoint> touch_points = event->touchPoints();
+ input_subsystem->GetTouchScreen()->ClearActiveFlag();
for (const auto& touch_point : touch_points) {
- if (!TouchUpdate(touch_point)) {
- TouchStart(touch_point);
- }
- }
- // Release all inactive points
- for (std::size_t id = 0; id < touch_ids.size(); ++id) {
- if (!TouchExist(touch_ids[id], touch_points)) {
- touch_ids[id] = 0;
- input_subsystem->GetTouchScreen()->TouchReleased(id);
- }
+ const auto [x, y] = ScaleTouch(touch_point.pos());
+ const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
+ input_subsystem->GetTouchScreen()->TouchMoved(touch_x, touch_y, touch_point.id());
}
+ input_subsystem->GetTouchScreen()->ReleaseInactiveTouch();
}
void GRenderWindow::TouchEndEvent() {
- for (std::size_t id = 0; id < touch_ids.size(); ++id) {
- if (touch_ids[id] != 0) {
- touch_ids[id] = 0;
- input_subsystem->GetTouchScreen()->TouchReleased(id);
+ input_subsystem->GetTouchScreen()->ReleaseAllTouch();
+}
+
+void GRenderWindow::InitializeCamera() {
+ constexpr auto camera_update_ms = std::chrono::milliseconds{50}; // (50ms, 20Hz)
+ if (!Settings::values.enable_ir_sensor) {
+ return;
+ }
+
+ bool camera_found = false;
+ const QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
+ for (const QCameraInfo& cameraInfo : cameras) {
+ if (Settings::values.ir_sensor_device.GetValue() == cameraInfo.deviceName().toStdString() ||
+ Settings::values.ir_sensor_device.GetValue() == "Auto") {
+ camera = std::make_unique<QCamera>(cameraInfo);
+ if (!camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder) &&
+ !camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) {
+ LOG_ERROR(Frontend,
+ "Camera doesn't support CaptureViewfinder or CaptureStillImage");
+ continue;
+ }
+ camera_found = true;
+ break;
}
}
+
+ if (!camera_found) {
+ return;
+ }
+
+ camera_capture = std::make_unique<QCameraImageCapture>(camera.get());
+
+ if (!camera_capture->isCaptureDestinationSupported(
+ QCameraImageCapture::CaptureDestination::CaptureToBuffer)) {
+ LOG_ERROR(Frontend, "Camera doesn't support saving to buffer");
+ return;
+ }
+
+ camera_capture->setCaptureDestination(QCameraImageCapture::CaptureDestination::CaptureToBuffer);
+ connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this,
+ &GRenderWindow::OnCameraCapture);
+ camera->unload();
+ if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder)) {
+ camera->setCaptureMode(QCamera::CaptureViewfinder);
+ } else if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) {
+ camera->setCaptureMode(QCamera::CaptureStillImage);
+ }
+ camera->load();
+ camera->start();
+
+ pending_camera_snapshots = 0;
+ is_virtual_camera = false;
+
+ camera_timer = std::make_unique<QTimer>();
+ connect(camera_timer.get(), &QTimer::timeout, [this] { RequestCameraCapture(); });
+ // This timer should be dependent of camera resolution 5ms for every 100 pixels
+ camera_timer->start(camera_update_ms);
}
-void GRenderWindow::TouchStart(const QTouchEvent::TouchPoint& touch_point) {
- for (std::size_t id = 0; id < touch_ids.size(); ++id) {
- if (touch_ids[id] == 0) {
- touch_ids[id] = touch_point.id() + 1;
- const auto [x, y] = ScaleTouch(touch_point.pos());
- const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
- input_subsystem->GetTouchScreen()->TouchPressed(touch_x, touch_y, id);
- }
+void GRenderWindow::FinalizeCamera() {
+ if (camera_timer) {
+ camera_timer->stop();
+ }
+ if (camera) {
+ camera->unload();
}
}
-bool GRenderWindow::TouchUpdate(const QTouchEvent::TouchPoint& touch_point) {
- for (std::size_t id = 0; id < touch_ids.size(); ++id) {
- if (touch_ids[id] == static_cast<std::size_t>(touch_point.id() + 1)) {
- const auto [x, y] = ScaleTouch(touch_point.pos());
- const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
- input_subsystem->GetTouchScreen()->TouchMoved(touch_x, touch_y, id);
- return true;
- }
+void GRenderWindow::RequestCameraCapture() {
+ if (!Settings::values.enable_ir_sensor) {
+ return;
}
- return false;
+
+ // If the camera doesn't capture, test for virtual cameras
+ if (pending_camera_snapshots > 5) {
+ is_virtual_camera = true;
+ }
+ // Virtual cameras like obs need to reset the camera every capture
+ if (is_virtual_camera) {
+ camera->stop();
+ camera->start();
+ }
+
+ pending_camera_snapshots++;
+ camera_capture->capture();
}
-bool GRenderWindow::TouchExist(std::size_t id,
- const QList<QTouchEvent::TouchPoint>& touch_points) const {
- return std::any_of(touch_points.begin(), touch_points.end(), [id](const auto& point) {
- return id == static_cast<std::size_t>(point.id() + 1);
- });
+void GRenderWindow::OnCameraCapture(int requestId, const QImage& img) {
+ constexpr std::size_t camera_width = 320;
+ constexpr std::size_t camera_height = 240;
+ const auto converted =
+ img.scaled(camera_width, camera_height, Qt::AspectRatioMode::IgnoreAspectRatio,
+ Qt::TransformationMode::SmoothTransformation)
+ .mirrored(false, true);
+ std::vector<u32> camera_data{};
+ camera_data.resize(camera_width * camera_height);
+ std::memcpy(camera_data.data(), converted.bits(), camera_width * camera_height * sizeof(u32));
+ input_subsystem->GetCamera()->SetCameraData(camera_width, camera_height, camera_data);
+ pending_camera_snapshots = 0;
}
bool GRenderWindow::event(QEvent* event) {
@@ -936,6 +1001,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(
@@ -1036,8 +1107,8 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
}
if (!unsupported_ext.empty()) {
- LOG_ERROR(Frontend, "GPU does not support all required extensions: {}",
- glGetString(GL_RENDERER));
+ const std::string gl_renderer{reinterpret_cast<const char*>(glGetString(GL_RENDERER))};
+ LOG_ERROR(Frontend, "GPU does not support all required extensions: {}", gl_renderer);
}
for (const QString& ext : unsupported_ext) {
LOG_ERROR(Frontend, "Unsupported GL extension: {}", ext.toStdString());
@@ -1046,8 +1117,8 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
return unsupported_ext;
}
-void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
- this->emu_thread = emu_thread;
+void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread_) {
+ emu_thread = emu_thread_;
}
void GRenderWindow::OnEmulationStopping() {
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 92297a43b..c45ebf1a2 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -10,18 +9,19 @@
#include <mutex>
#include <QImage>
+#include <QStringList>
#include <QThread>
#include <QTouchEvent>
#include <QWidget>
-#include <QWindow>
#include "common/thread.h"
#include "core/frontend/emu_window.h"
class GRenderWindow;
class GMainWindow;
+class QCamera;
+class QCameraImageCapture;
class QKeyEvent;
-class QStringList;
namespace Core {
enum class SystemResultStatus : u32;
@@ -56,22 +56,13 @@ public:
void run() override;
/**
- * Steps the emulation thread by a single CPU instruction (if the CPU is not already running)
- * @note This function is thread-safe
- */
- void ExecStep() {
- exec_step = true;
- running_cv.notify_all();
- }
-
- /**
* Sets whether the emulation thread is running or not
- * @param running Boolean value, set the emulation thread to running if true
+ * @param running_ Boolean value, set the emulation thread to running if true
* @note This function is thread-safe
*/
- void SetRunning(bool running) {
+ void SetRunning(bool running_) {
std::unique_lock lock{running_mutex};
- this->running = running;
+ running = running_;
lock.unlock();
running_cv.notify_all();
if (!running) {
@@ -100,7 +91,6 @@ public:
}
private:
- bool exec_step = false;
bool running = false;
std::stop_source stop_source;
std::mutex running_mutex;
@@ -149,8 +139,8 @@ public:
void BackupGeometry();
void RestoreGeometry();
- void restoreGeometry(const QByteArray& geometry); // overridden
- QByteArray saveGeometry(); // overridden
+ void restoreGeometry(const QByteArray& geometry_); // overridden
+ QByteArray saveGeometry(); // overridden
qreal windowPixelRatio() const;
@@ -175,6 +165,9 @@ public:
void mouseReleaseEvent(QMouseEvent* event) override;
void wheelEvent(QWheelEvent* event) override;
+ void InitializeCamera();
+ void FinalizeCamera();
+
bool event(QEvent* event) override;
void focusOutEvent(QFocusEvent* event) override;
@@ -200,7 +193,7 @@ public:
void Exit();
public slots:
- void OnEmulationStarting(EmuThread* emu_thread);
+ void OnEmulationStarting(EmuThread* emu_thread_);
void OnEmulationStopping();
void OnFramebufferSizeChanged();
@@ -218,9 +211,8 @@ private:
void TouchUpdateEvent(const QTouchEvent* event);
void TouchEndEvent();
- void TouchStart(const QTouchEvent::TouchPoint& touch_point);
- bool TouchUpdate(const QTouchEvent::TouchPoint& touch_point);
- bool TouchExist(std::size_t id, const QList<QTouchEvent::TouchPoint>& touch_points) const;
+ void RequestCameraCapture();
+ void OnCameraCapture(int requestId, const QImage& img);
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
@@ -247,7 +239,11 @@ private:
bool first_frame = false;
InputCommon::TasInput::TasState last_tas_state;
- std::array<std::size_t, 16> touch_ids{};
+ bool is_virtual_camera;
+ int pending_camera_snapshots;
+ std::unique_ptr<QCamera> camera;
+ std::unique_ptr<QCameraImageCapture> camera_capture;
+ std::unique_ptr<QTimer> camera_timer;
Core::System& system;
diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp
index 2442bb3c3..f46fff340 100644
--- a/src/yuzu/compatdb.cpp
+++ b/src/yuzu/compatdb.cpp
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QButtonGroup>
#include <QMessageBox>
diff --git a/src/yuzu/compatdb.h b/src/yuzu/compatdb.h
index e2b2522bd..3252fc47a 100644
--- a/src/yuzu/compatdb.h
+++ b/src/yuzu/compatdb.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
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/compatibility_list.cpp b/src/yuzu/compatibility_list.cpp
index 2d2cfd03c..dbbe76448 100644
--- a/src/yuzu/compatibility_list.cpp
+++ b/src/yuzu/compatibility_list.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
diff --git a/src/yuzu/compatibility_list.h b/src/yuzu/compatibility_list.h
index bc0175bd3..c0675d793 100644
--- a/src/yuzu/compatibility_list.h
+++ b/src/yuzu/compatibility_list.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 9ee7992e7..195074bf2 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <QKeySequence>
@@ -11,12 +10,12 @@
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "input_common/main.h"
+#include "network/network.h"
#include "yuzu/configuration/config.h"
namespace FS = Common::FS;
-Config::Config(Core::System& system_, const std::string& config_name, ConfigType config_type)
- : type(config_type), system{system_} {
+Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) {
global = config_type == ConfigType::GlobalConfig;
Initialize(config_name);
@@ -60,34 +59,39 @@ 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
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{
- {QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut}},
- {QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut}},
- {QStringLiteral("Audio Volume Up"), QStringLiteral("Main Window"), {QStringLiteral("+"), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}},
- {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}},
- {QStringLiteral("Change Adapting Filter"), QStringLiteral("Main Window"), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut}},
- {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}},
- {QStringLiteral("Change GPU Accuracy"), QStringLiteral("Main Window"), {QStringLiteral("F9"), QStringLiteral("Home+R"), Qt::ApplicationShortcut}},
- {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut}},
- {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut}},
- {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut}},
- {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut}},
- {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut}},
- {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}},
- {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut}},
- {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut}},
- {QStringLiteral("TAS Start/Stop"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut}},
- {QStringLiteral("TAS Reset"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut}},
- {QStringLiteral("TAS Record"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut}},
- {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut}},
- {QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut}},
- {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut}},
- {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("="), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Accuracy")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F9"), QStringLiteral("Home+R"), Qt::ApplicationShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit yuzu")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut}},
}};
// clang-format on
@@ -128,7 +132,7 @@ void Config::Initialize(const std::string& config_name) {
// Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant, nor
// can it implicitly convert a QVariant back to a {std::,Q}string
template <>
-void Config::ReadBasicSetting(Settings::BasicSetting<std::string>& setting) {
+void Config::ReadBasicSetting(Settings::Setting<std::string>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const auto default_value = QString::fromStdString(setting.GetDefault());
if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
@@ -138,8 +142,8 @@ void Config::ReadBasicSetting(Settings::BasicSetting<std::string>& setting) {
}
}
-template <typename Type>
-void Config::ReadBasicSetting(Settings::BasicSetting<Type>& setting) {
+template <typename Type, bool ranged>
+void Config::ReadBasicSetting(Settings::Setting<Type, ranged>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const Type default_value = setting.GetDefault();
if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
@@ -152,23 +156,23 @@ void Config::ReadBasicSetting(Settings::BasicSetting<Type>& setting) {
// Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant
template <>
-void Config::WriteBasicSetting(const Settings::BasicSetting<std::string>& setting) {
+void Config::WriteBasicSetting(const Settings::Setting<std::string>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const std::string& value = setting.GetValue();
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
qt_config->setValue(name, QString::fromStdString(value));
}
-template <typename Type>
-void Config::WriteBasicSetting(const Settings::BasicSetting<Type>& setting) {
+template <typename Type, bool ranged>
+void Config::WriteBasicSetting(const Settings::Setting<Type, ranged>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const Type value = setting.GetValue();
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
qt_config->setValue(name, value);
}
-template <typename Type>
-void Config::WriteGlobalSetting(const Settings::Setting<Type>& setting) {
+template <typename Type, bool ranged>
+void Config::WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const Type& value = setting.GetValue(global);
if (!global) {
@@ -346,12 +350,35 @@ 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::ReadIrCameraValues() {
+ ReadBasicSetting(Settings::values.enable_ir_sensor);
+ ReadBasicSetting(Settings::values.ir_sensor_device);
+}
+
void Config::ReadAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
if (global) {
- ReadBasicSetting(Settings::values.audio_device_id);
ReadBasicSetting(Settings::values.sink_id);
+ ReadBasicSetting(Settings::values.audio_output_device_id);
+ ReadBasicSetting(Settings::values.audio_input_device_id);
}
ReadGlobalSetting(Settings::values.volume);
@@ -369,6 +396,8 @@ void Config::ReadControlValues() {
ReadMouseValues();
ReadTouchscreenValues();
ReadMotionTouchValues();
+ ReadHidbusValues();
+ ReadIrCameraValues();
#ifdef _WIN32
ReadBasicSetting(Settings::values.enable_raw_input);
@@ -445,6 +474,7 @@ void Config::ReadCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
ReadGlobalSetting(Settings::values.use_multi_core);
+ ReadGlobalSetting(Settings::values.use_extended_memory_layout);
qt_config->endGroup();
}
@@ -501,6 +531,9 @@ void Config::ReadDebuggingValues() {
// Intentionally not using the QT default setting as this is intended to be changed in the ini
Settings::values.record_frame_times =
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
+
+ ReadBasicSetting(Settings::values.use_gdbstub);
+ ReadBasicSetting(Settings::values.gdbstub_port);
ReadBasicSetting(Settings::values.program_args);
ReadBasicSetting(Settings::values.dump_exefs);
ReadBasicSetting(Settings::values.dump_nso);
@@ -512,6 +545,8 @@ void Config::ReadDebuggingValues() {
ReadBasicSetting(Settings::values.use_debug_asserts);
ReadBasicSetting(Settings::values.use_auto_stub);
ReadBasicSetting(Settings::values.enable_all_controllers);
+ ReadBasicSetting(Settings::values.create_crash_dumps);
+ ReadBasicSetting(Settings::values.perform_vulkan_check);
qt_config->endGroup();
}
@@ -608,6 +643,7 @@ void Config::ReadCpuValues() {
ReadGlobalSetting(Settings::values.cpuopt_unsafe_ignore_standard_fpcr);
ReadGlobalSetting(Settings::values.cpuopt_unsafe_inaccurate_nan);
ReadGlobalSetting(Settings::values.cpuopt_unsafe_fastmem_check);
+ ReadGlobalSetting(Settings::values.cpuopt_unsafe_ignore_global_monitor);
if (global) {
ReadBasicSetting(Settings::values.cpu_debug_mode);
@@ -620,6 +656,8 @@ void Config::ReadCpuValues() {
ReadBasicSetting(Settings::values.cpuopt_misc_ir);
ReadBasicSetting(Settings::values.cpuopt_reduce_misalign_checks);
ReadBasicSetting(Settings::values.cpuopt_fastmem);
+ ReadBasicSetting(Settings::values.cpuopt_fastmem_exclusives);
+ ReadBasicSetting(Settings::values.cpuopt_recompile_exclusives);
}
qt_config->endGroup();
@@ -638,7 +676,6 @@ void Config::ReadRendererValues() {
ReadGlobalSetting(Settings::values.max_anisotropy);
ReadGlobalSetting(Settings::values.use_speed_limit);
ReadGlobalSetting(Settings::values.speed_limit);
- ReadGlobalSetting(Settings::values.fps_cap);
ReadGlobalSetting(Settings::values.use_disk_shader_cache);
ReadGlobalSetting(Settings::values.gpu_accuracy);
ReadGlobalSetting(Settings::values.use_asynchronous_gpu_emulation);
@@ -648,6 +685,7 @@ void Config::ReadRendererValues() {
ReadGlobalSetting(Settings::values.shader_backend);
ReadGlobalSetting(Settings::values.use_asynchronous_shaders);
ReadGlobalSetting(Settings::values.use_fast_gpu_time);
+ ReadGlobalSetting(Settings::values.use_pessimistic_flushes);
ReadGlobalSetting(Settings::values.bg_red);
ReadGlobalSetting(Settings::values.bg_green);
ReadGlobalSetting(Settings::values.bg_blue);
@@ -758,6 +796,7 @@ void Config::ReadUIValues() {
ReadPathValues();
ReadScreenshotValues();
ReadShortcutValues();
+ ReadMultiplayerValues();
ReadBasicSetting(UISettings::values.single_window_mode);
ReadBasicSetting(UISettings::values.fullscreen);
@@ -771,6 +810,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();
}
@@ -823,6 +863,42 @@ void Config::ReadWebServiceValues() {
qt_config->endGroup();
}
+void Config::ReadMultiplayerValues() {
+ qt_config->beginGroup(QStringLiteral("Multiplayer"));
+
+ ReadBasicSetting(UISettings::values.multiplayer_nickname);
+ ReadBasicSetting(UISettings::values.multiplayer_ip);
+ ReadBasicSetting(UISettings::values.multiplayer_port);
+ ReadBasicSetting(UISettings::values.multiplayer_room_nickname);
+ ReadBasicSetting(UISettings::values.multiplayer_room_name);
+ ReadBasicSetting(UISettings::values.multiplayer_room_port);
+ ReadBasicSetting(UISettings::values.multiplayer_host_type);
+ ReadBasicSetting(UISettings::values.multiplayer_port);
+ ReadBasicSetting(UISettings::values.multiplayer_max_player);
+ ReadBasicSetting(UISettings::values.multiplayer_game_id);
+ ReadBasicSetting(UISettings::values.multiplayer_room_description);
+
+ // Read ban list back
+ int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
+ UISettings::values.multiplayer_ban_list.first.resize(size);
+ for (int i = 0; i < size; ++i) {
+ qt_config->setArrayIndex(i);
+ UISettings::values.multiplayer_ban_list.first[i] =
+ ReadSetting(QStringLiteral("username")).toString().toStdString();
+ }
+ qt_config->endArray();
+ size = qt_config->beginReadArray(QStringLiteral("ip_ban_list"));
+ UISettings::values.multiplayer_ban_list.second.resize(size);
+ for (int i = 0; i < size; ++i) {
+ qt_config->setArrayIndex(i);
+ UISettings::values.multiplayer_ban_list.second[i] =
+ ReadSetting(QStringLiteral("ip")).toString().toStdString();
+ }
+ qt_config->endArray();
+
+ qt_config->endGroup();
+}
+
void Config::ReadValues() {
if (global) {
ReadControlValues();
@@ -839,6 +915,7 @@ void Config::ReadValues() {
ReadRendererValues();
ReadAudioValues();
ReadSystemValues();
+ ReadMultiplayerValues();
}
void Config::SavePlayerValue(std::size_t player_index) {
@@ -957,6 +1034,21 @@ 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::SaveIrCameraValues() {
+ WriteBasicSetting(Settings::values.enable_ir_sensor);
+ WriteBasicSetting(Settings::values.ir_sensor_device);
+}
+
void Config::SaveValues() {
if (global) {
SaveControlValues();
@@ -973,6 +1065,7 @@ void Config::SaveValues() {
SaveRendererValues();
SaveAudioValues();
SaveSystemValues();
+ SaveMultiplayerValues();
}
void Config::SaveAudioValues() {
@@ -980,7 +1073,8 @@ void Config::SaveAudioValues() {
if (global) {
WriteBasicSetting(Settings::values.sink_id);
- WriteBasicSetting(Settings::values.audio_device_id);
+ WriteBasicSetting(Settings::values.audio_output_device_id);
+ WriteBasicSetting(Settings::values.audio_input_device_id);
}
WriteGlobalSetting(Settings::values.volume);
@@ -997,6 +1091,8 @@ void Config::SaveControlValues() {
SaveMouseValues();
SaveTouchscreenValues();
SaveMotionTouchValues();
+ SaveHidbusValues();
+ SaveIrCameraValues();
WriteGlobalSetting(Settings::values.use_docked_mode);
WriteGlobalSetting(Settings::values.vibration_enabled);
@@ -1019,6 +1115,7 @@ void Config::SaveCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
WriteGlobalSetting(Settings::values.use_multi_core);
+ WriteGlobalSetting(Settings::values.use_extended_memory_layout);
qt_config->endGroup();
}
@@ -1055,6 +1152,8 @@ void Config::SaveDebuggingValues() {
// Intentionally not using the QT default setting as this is intended to be changed in the ini
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
+ WriteBasicSetting(Settings::values.use_gdbstub);
+ WriteBasicSetting(Settings::values.gdbstub_port);
WriteBasicSetting(Settings::values.program_args);
WriteBasicSetting(Settings::values.dump_exefs);
WriteBasicSetting(Settings::values.dump_nso);
@@ -1063,6 +1162,8 @@ void Config::SaveDebuggingValues() {
WriteBasicSetting(Settings::values.use_debug_asserts);
WriteBasicSetting(Settings::values.disable_macro_jit);
WriteBasicSetting(Settings::values.enable_all_controllers);
+ WriteBasicSetting(Settings::values.create_crash_dumps);
+ WriteBasicSetting(Settings::values.perform_vulkan_check);
qt_config->endGroup();
}
@@ -1137,6 +1238,7 @@ void Config::SaveCpuValues() {
WriteGlobalSetting(Settings::values.cpuopt_unsafe_ignore_standard_fpcr);
WriteGlobalSetting(Settings::values.cpuopt_unsafe_inaccurate_nan);
WriteGlobalSetting(Settings::values.cpuopt_unsafe_fastmem_check);
+ WriteGlobalSetting(Settings::values.cpuopt_unsafe_ignore_global_monitor);
if (global) {
WriteBasicSetting(Settings::values.cpu_debug_mode);
@@ -1149,6 +1251,8 @@ void Config::SaveCpuValues() {
WriteBasicSetting(Settings::values.cpuopt_misc_ir);
WriteBasicSetting(Settings::values.cpuopt_reduce_misalign_checks);
WriteBasicSetting(Settings::values.cpuopt_fastmem);
+ WriteBasicSetting(Settings::values.cpuopt_fastmem_exclusives);
+ WriteBasicSetting(Settings::values.cpuopt_recompile_exclusives);
}
qt_config->endGroup();
@@ -1182,7 +1286,6 @@ void Config::SaveRendererValues() {
WriteGlobalSetting(Settings::values.max_anisotropy);
WriteGlobalSetting(Settings::values.use_speed_limit);
WriteGlobalSetting(Settings::values.speed_limit);
- WriteGlobalSetting(Settings::values.fps_cap);
WriteGlobalSetting(Settings::values.use_disk_shader_cache);
WriteSetting(QString::fromStdString(Settings::values.gpu_accuracy.GetLabel()),
static_cast<u32>(Settings::values.gpu_accuracy.GetValue(global)),
@@ -1201,6 +1304,7 @@ void Config::SaveRendererValues() {
Settings::values.shader_backend.UsingGlobal());
WriteGlobalSetting(Settings::values.use_asynchronous_shaders);
WriteGlobalSetting(Settings::values.use_fast_gpu_time);
+ WriteGlobalSetting(Settings::values.use_pessimistic_flushes);
WriteGlobalSetting(Settings::values.bg_red);
WriteGlobalSetting(Settings::values.bg_green);
WriteGlobalSetting(Settings::values.bg_blue);
@@ -1287,6 +1391,7 @@ void Config::SaveUIValues() {
SavePathValues();
SaveScreenshotValues();
SaveShortcutValues();
+ SaveMultiplayerValues();
WriteBasicSetting(UISettings::values.single_window_mode);
WriteBasicSetting(UISettings::values.fullscreen);
@@ -1300,6 +1405,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();
}
@@ -1350,6 +1456,40 @@ void Config::SaveWebServiceValues() {
qt_config->endGroup();
}
+void Config::SaveMultiplayerValues() {
+ qt_config->beginGroup(QStringLiteral("Multiplayer"));
+
+ WriteBasicSetting(UISettings::values.multiplayer_nickname);
+ WriteBasicSetting(UISettings::values.multiplayer_ip);
+ WriteBasicSetting(UISettings::values.multiplayer_port);
+ WriteBasicSetting(UISettings::values.multiplayer_room_nickname);
+ WriteBasicSetting(UISettings::values.multiplayer_room_name);
+ WriteBasicSetting(UISettings::values.multiplayer_room_port);
+ WriteBasicSetting(UISettings::values.multiplayer_host_type);
+ WriteBasicSetting(UISettings::values.multiplayer_port);
+ WriteBasicSetting(UISettings::values.multiplayer_max_player);
+ WriteBasicSetting(UISettings::values.multiplayer_game_id);
+ WriteBasicSetting(UISettings::values.multiplayer_room_description);
+
+ // Write ban list
+ qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
+ for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) {
+ qt_config->setArrayIndex(static_cast<int>(i));
+ WriteSetting(QStringLiteral("username"),
+ QString::fromStdString(UISettings::values.multiplayer_ban_list.first[i]));
+ }
+ qt_config->endArray();
+ qt_config->beginWriteArray(QStringLiteral("ip_ban_list"));
+ for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) {
+ qt_config->setArrayIndex(static_cast<int>(i));
+ WriteSetting(QStringLiteral("ip"),
+ QString::fromStdString(UISettings::values.multiplayer_ban_list.second[i]));
+ }
+ qt_config->endArray();
+
+ qt_config->endGroup();
+}
+
QVariant Config::ReadSetting(const QString& name) const {
return qt_config->value(name);
}
@@ -1364,8 +1504,8 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value)
return result;
}
-template <typename Type>
-void Config::ReadGlobalSetting(Settings::Setting<Type>& setting) {
+template <typename Type, bool ranged>
+void Config::ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting) {
QString name = QString::fromStdString(setting.GetLabel());
const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
setting.SetGlobal(use_global);
@@ -1410,7 +1550,6 @@ void Config::Reload() {
ReadValues();
// To apply default value changes
SaveValues();
- system.ApplySettings();
}
void Config::Save() {
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index ae3e36a11..06fa7d2d0 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -26,7 +25,7 @@ public:
InputProfile,
};
- explicit Config(Core::System& system_, const std::string& config_name = "qt-config",
+ explicit Config(const std::string& config_name = "qt-config",
ConfigType config_type = ConfigType::GlobalConfig);
~Config();
@@ -42,6 +41,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 +66,8 @@ private:
void ReadMouseValues();
void ReadTouchscreenValues();
void ReadMotionTouchValues();
+ void ReadHidbusValues();
+ void ReadIrCameraValues();
// Read functions bases off the respective config section names.
void ReadAudioValues();
@@ -86,6 +88,7 @@ private:
void ReadUIGamelistValues();
void ReadUILayoutValues();
void ReadWebServiceValues();
+ void ReadMultiplayerValues();
void SaveValues();
void SavePlayerValue(std::size_t player_index);
@@ -93,6 +96,8 @@ private:
void SaveMouseValues();
void SaveTouchscreenValues();
void SaveMotionTouchValues();
+ void SaveHidbusValues();
+ void SaveIrCameraValues();
// Save functions based off the respective config section names.
void SaveAudioValues();
@@ -113,6 +118,7 @@ private:
void SaveUIGamelistValues();
void SaveUILayoutValues();
void SaveWebServiceValues();
+ void SaveMultiplayerValues();
/**
* Reads a setting from the qt_config.
@@ -156,8 +162,8 @@ private:
*
* @param The setting
*/
- template <typename Type>
- void ReadGlobalSetting(Settings::Setting<Type>& setting);
+ template <typename Type, bool ranged>
+ void ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting);
/**
* Sets a value to the qt_config using the setting's label and default value. If the config is a
@@ -165,8 +171,8 @@ private:
*
* @param The setting
*/
- template <typename Type>
- void WriteGlobalSetting(const Settings::Setting<Type>& setting);
+ template <typename Type, bool ranged>
+ void WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting);
/**
* Reads a value from the qt_config using the setting's label and default value and applies the
@@ -174,22 +180,20 @@ private:
*
* @param The setting
*/
- template <typename Type>
- void ReadBasicSetting(Settings::BasicSetting<Type>& setting);
+ template <typename Type, bool ranged>
+ void ReadBasicSetting(Settings::Setting<Type, ranged>& setting);
/** Sets a value from the setting in the qt_config using the setting's label and default value.
*
* @param The setting
*/
- template <typename Type>
- void WriteBasicSetting(const Settings::BasicSetting<Type>& setting);
+ template <typename Type, bool ranged>
+ void WriteBasicSetting(const Settings::Setting<Type, ranged>& setting);
ConfigType type;
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
bool global;
-
- Core::System& system;
};
// These metatype declarations cannot be in common/settings.h because core is devoid of QT
diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp
index 251aab912..97fb664bf 100644
--- a/src/yuzu/configuration/configuration_shared.cpp
+++ b/src/yuzu/configuration/configuration_shared.cpp
@@ -1,16 +1,14 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QCheckBox>
-#include <QComboBox>
#include <QObject>
#include <QString>
#include "common/settings.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_per_game.h"
-void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<bool>* setting,
+void ConfigurationShared::ApplyPerGameSetting(Settings::SwitchableSetting<bool>* setting,
const QCheckBox* checkbox,
const CheckState& tracker) {
if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) {
@@ -26,7 +24,7 @@ void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<bool>* setting,
}
void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox,
- const Settings::Setting<bool>* setting) {
+ const Settings::SwitchableSetting<bool>* setting) {
if (setting->UsingGlobal()) {
checkbox->setCheckState(Qt::PartiallyChecked);
} else {
@@ -46,7 +44,7 @@ void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) {
}
void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox,
- const Settings::Setting<bool>& setting,
+ const Settings::SwitchableSetting<bool>& setting,
CheckState& tracker) {
if (setting.UsingGlobal()) {
tracker = CheckState::Global;
diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h
index 5423dbc92..e597dcdb5 100644
--- a/src/yuzu/configuration/configuration_shared.h
+++ b/src/yuzu/configuration/configuration_shared.h
@@ -1,12 +1,10 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QCheckBox>
#include <QComboBox>
-#include <QString>
#include "common/settings.h"
namespace ConfigurationShared {
@@ -26,10 +24,11 @@ enum class CheckState {
// Global-aware apply and set functions
// ApplyPerGameSetting, given a Settings::Setting and a Qt UI element, properly applies a Setting
-void ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox,
+void ApplyPerGameSetting(Settings::SwitchableSetting<bool>* setting, const QCheckBox* checkbox,
const CheckState& tracker);
-template <typename Type>
-void ApplyPerGameSetting(Settings::Setting<Type>* setting, const QComboBox* combobox) {
+template <typename Type, bool ranged>
+void ApplyPerGameSetting(Settings::SwitchableSetting<Type, ranged>* setting,
+ const QComboBox* combobox) {
if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) {
setting->SetValue(static_cast<Type>(combobox->currentIndex()));
} else if (!Settings::IsConfiguringGlobal()) {
@@ -44,10 +43,11 @@ void ApplyPerGameSetting(Settings::Setting<Type>* setting, const QComboBox* comb
}
// Sets a Qt UI element given a Settings::Setting
-void SetPerGameSetting(QCheckBox* checkbox, const Settings::Setting<bool>* setting);
+void SetPerGameSetting(QCheckBox* checkbox, const Settings::SwitchableSetting<bool>* setting);
-template <typename Type>
-void SetPerGameSetting(QComboBox* combobox, const Settings::Setting<Type>* setting) {
+template <typename Type, bool ranged>
+void SetPerGameSetting(QComboBox* combobox,
+ const Settings::SwitchableSetting<Type, ranged>* setting) {
combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX
: static_cast<int>(setting->GetValue()) +
ConfigurationShared::USE_GLOBAL_OFFSET);
@@ -57,7 +57,7 @@ void SetPerGameSetting(QComboBox* combobox, const Settings::Setting<Type>* setti
void SetHighlight(QWidget* widget, bool highlighted);
// Sets up a QCheckBox like a tristate one, given a Setting
-void SetColoredTristate(QCheckBox* checkbox, const Settings::Setting<bool>& setting,
+void SetColoredTristate(QCheckBox* checkbox, const Settings::SwitchableSetting<bool>& setting,
CheckState& tracker);
void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state,
CheckState& tracker);
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index c33488718..19b8b15ef 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -1,13 +1,10 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
-#include <QSignalBlocker>
-
-#include "audio_core/sink.h"
-#include "audio_core/sink_details.h"
+#include "audio_core/sink/sink.h"
+#include "audio_core/sink/sink_details.h"
#include "common/settings.h"
#include "core/core.h"
#include "ui_configure_audio.h"
@@ -18,11 +15,11 @@ ConfigureAudio::ConfigureAudio(const Core::System& system_, QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()), system{system_} {
ui->setupUi(this);
- InitializeAudioOutputSinkComboBox();
+ InitializeAudioSinkComboBox();
connect(ui->volume_slider, &QSlider::valueChanged, this,
&ConfigureAudio::SetVolumeIndicatorText);
- connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ connect(ui->sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureAudio::UpdateAudioDevices);
ui->volume_label->setVisible(Settings::IsConfiguringGlobal());
@@ -33,8 +30,9 @@ ConfigureAudio::ConfigureAudio(const Core::System& system_, QWidget* parent)
SetConfiguration();
const bool is_powered_on = system_.IsPoweredOn();
- ui->output_sink_combo_box->setEnabled(!is_powered_on);
- ui->audio_device_combo_box->setEnabled(!is_powered_on);
+ ui->sink_combo_box->setEnabled(!is_powered_on);
+ ui->output_combo_box->setEnabled(!is_powered_on);
+ ui->input_combo_box->setEnabled(!is_powered_on);
}
ConfigureAudio::~ConfigureAudio() = default;
@@ -43,9 +41,9 @@ void ConfigureAudio::SetConfiguration() {
SetOutputSinkFromSinkID();
// The device list cannot be pre-populated (nor listed) until the output sink is known.
- UpdateAudioDevices(ui->output_sink_combo_box->currentIndex());
+ UpdateAudioDevices(ui->sink_combo_box->currentIndex());
- SetAudioDeviceFromDeviceID();
+ SetAudioDevicesFromDeviceID();
const auto volume_value = static_cast<int>(Settings::values.volume.GetValue());
ui->volume_slider->setValue(volume_value);
@@ -65,32 +63,45 @@ void ConfigureAudio::SetConfiguration() {
}
void ConfigureAudio::SetOutputSinkFromSinkID() {
- [[maybe_unused]] const QSignalBlocker blocker(ui->output_sink_combo_box);
+ [[maybe_unused]] const QSignalBlocker blocker(ui->sink_combo_box);
int new_sink_index = 0;
const QString sink_id = QString::fromStdString(Settings::values.sink_id.GetValue());
- for (int index = 0; index < ui->output_sink_combo_box->count(); index++) {
- if (ui->output_sink_combo_box->itemText(index) == sink_id) {
+ for (int index = 0; index < ui->sink_combo_box->count(); index++) {
+ if (ui->sink_combo_box->itemText(index) == sink_id) {
new_sink_index = index;
break;
}
}
- ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
+ ui->sink_combo_box->setCurrentIndex(new_sink_index);
}
-void ConfigureAudio::SetAudioDeviceFromDeviceID() {
+void ConfigureAudio::SetAudioDevicesFromDeviceID() {
int new_device_index = -1;
- const QString device_id = QString::fromStdString(Settings::values.audio_device_id.GetValue());
- for (int index = 0; index < ui->audio_device_combo_box->count(); index++) {
- if (ui->audio_device_combo_box->itemText(index) == device_id) {
+ const QString output_device_id =
+ QString::fromStdString(Settings::values.audio_output_device_id.GetValue());
+ for (int index = 0; index < ui->output_combo_box->count(); index++) {
+ if (ui->output_combo_box->itemText(index) == output_device_id) {
+ new_device_index = index;
+ break;
+ }
+ }
+
+ ui->output_combo_box->setCurrentIndex(new_device_index);
+
+ new_device_index = -1;
+ const QString input_device_id =
+ QString::fromStdString(Settings::values.audio_input_device_id.GetValue());
+ for (int index = 0; index < ui->input_combo_box->count(); index++) {
+ if (ui->input_combo_box->itemText(index) == input_device_id) {
new_device_index = index;
break;
}
}
- ui->audio_device_combo_box->setCurrentIndex(new_device_index);
+ ui->input_combo_box->setCurrentIndex(new_device_index);
}
void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
@@ -98,14 +109,13 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
}
void ConfigureAudio::ApplyConfiguration() {
-
if (Settings::IsConfiguringGlobal()) {
Settings::values.sink_id =
- ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
- .toStdString();
- Settings::values.audio_device_id.SetValue(
- ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex())
- .toStdString());
+ ui->sink_combo_box->itemText(ui->sink_combo_box->currentIndex()).toStdString();
+ Settings::values.audio_output_device_id.SetValue(
+ ui->output_combo_box->itemText(ui->output_combo_box->currentIndex()).toStdString());
+ Settings::values.audio_input_device_id.SetValue(
+ ui->input_combo_box->itemText(ui->input_combo_box->currentIndex()).toStdString());
// Guard if during game and set to game-specific value
if (Settings::values.volume.UsingGlobal()) {
@@ -132,21 +142,27 @@ void ConfigureAudio::changeEvent(QEvent* event) {
}
void ConfigureAudio::UpdateAudioDevices(int sink_index) {
- ui->audio_device_combo_box->clear();
- ui->audio_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));
+ ui->output_combo_box->clear();
+ ui->output_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
+
+ const std::string sink_id = ui->sink_combo_box->itemText(sink_index).toStdString();
+ for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, false)) {
+ ui->output_combo_box->addItem(QString::fromStdString(device));
+ }
- const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
- for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) {
- ui->audio_device_combo_box->addItem(QString::fromStdString(device));
+ ui->input_combo_box->clear();
+ ui->input_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
+ for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, true)) {
+ ui->input_combo_box->addItem(QString::fromStdString(device));
}
}
-void ConfigureAudio::InitializeAudioOutputSinkComboBox() {
- ui->output_sink_combo_box->clear();
- ui->output_sink_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));
+void ConfigureAudio::InitializeAudioSinkComboBox() {
+ ui->sink_combo_box->clear();
+ ui->sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
- for (const char* id : AudioCore::GetSinkIDs()) {
- ui->output_sink_combo_box->addItem(QString::fromUtf8(id));
+ for (const char* id : AudioCore::Sink::GetSinkIDs()) {
+ ui->sink_combo_box->addItem(QString::fromUtf8(id));
}
}
@@ -167,8 +183,10 @@ void ConfigureAudio::SetupPerGameUI() {
ConfigurationShared::SetHighlight(ui->volume_layout, index == 1);
});
- ui->output_sink_combo_box->setVisible(false);
- ui->output_sink_label->setVisible(false);
- ui->audio_device_combo_box->setVisible(false);
- ui->audio_device_label->setVisible(false);
+ ui->sink_combo_box->setVisible(false);
+ ui->sink_label->setVisible(false);
+ ui->output_combo_box->setVisible(false);
+ ui->output_label->setVisible(false);
+ ui->input_combo_box->setVisible(false);
+ ui->input_label->setVisible(false);
}
diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h
index 5d2d05e47..0d03aae1d 100644
--- a/src/yuzu/configuration/configure_audio.h
+++ b/src/yuzu/configuration/configure_audio.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -32,14 +31,14 @@ public:
private:
void changeEvent(QEvent* event) override;
- void InitializeAudioOutputSinkComboBox();
+ void InitializeAudioSinkComboBox();
void RetranslateUI();
void UpdateAudioDevices(int sink_index);
void SetOutputSinkFromSinkID();
- void SetAudioDeviceFromDeviceID();
+ void SetAudioDevicesFromDeviceID();
void SetVolumeIndicatorText(int percentage);
void SetupPerGameUI();
diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui
index d1ac8ad02..6034d8581 100644
--- a/src/yuzu/configuration/configure_audio.ui
+++ b/src/yuzu/configuration/configure_audio.ui
@@ -21,30 +21,44 @@
</property>
<layout class="QVBoxLayout">
<item>
- <layout class="QHBoxLayout" name="_3">
+ <layout class="QHBoxLayout" name="engine_layout">
<item>
- <widget class="QLabel" name="output_sink_label">
+ <widget class="QLabel" name="sink_label">
<property name="text">
<string>Output Engine:</string>
</property>
</widget>
</item>
<item>
- <widget class="QComboBox" name="output_sink_combo_box"/>
+ <widget class="QComboBox" name="sink_combo_box"/>
</item>
</layout>
</item>
<item>
- <layout class="QHBoxLayout" name="_2">
+ <layout class="QHBoxLayout" name="output_layout">
<item>
- <widget class="QLabel" name="audio_device_label">
+ <widget class="QLabel" name="output_label">
<property name="text">
- <string>Audio Device:</string>
+ <string>Output Device</string>
</property>
</widget>
</item>
<item>
- <widget class="QComboBox" name="audio_device_combo_box"/>
+ <widget class="QComboBox" name="output_combo_box"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="input_layout">
+ <item>
+ <widget class="QLabel" name="input_label">
+ <property name="text">
+ <string>Input Device</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="input_combo_box"/>
</item>
</layout>
</item>
@@ -106,10 +120,10 @@
</sizepolicy>
</property>
<property name="maximum">
- <number>100</number>
+ <number>200</number>
</property>
<property name="pageStep">
- <number>10</number>
+ <number>5</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
diff --git a/src/yuzu/configuration/configure_camera.cpp b/src/yuzu/configuration/configure_camera.cpp
new file mode 100644
index 000000000..2a61de2a1
--- /dev/null
+++ b/src/yuzu/configuration/configure_camera.cpp
@@ -0,0 +1,156 @@
+// Text : Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <memory>
+#include <QCameraImageCapture>
+#include <QCameraInfo>
+#include <QStandardItemModel>
+#include <QTimer>
+
+#include "input_common/drivers/camera.h"
+#include "input_common/main.h"
+#include "ui_configure_camera.h"
+#include "yuzu/configuration/config.h"
+#include "yuzu/configuration/configure_camera.h"
+
+ConfigureCamera::ConfigureCamera(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_)
+ : QDialog(parent), input_subsystem{input_subsystem_},
+ ui(std::make_unique<Ui::ConfigureCamera>()) {
+ ui->setupUi(this);
+
+ connect(ui->restore_defaults_button, &QPushButton::clicked, this,
+ &ConfigureCamera::RestoreDefaults);
+ connect(ui->preview_button, &QPushButton::clicked, this, &ConfigureCamera::PreviewCamera);
+
+ auto blank_image = QImage(320, 240, QImage::Format::Format_RGB32);
+ blank_image.fill(Qt::black);
+ DisplayCapturedFrame(0, blank_image);
+
+ LoadConfiguration();
+ resize(0, 0);
+}
+
+ConfigureCamera::~ConfigureCamera() = default;
+
+void ConfigureCamera::PreviewCamera() {
+ const auto index = ui->ir_sensor_combo_box->currentIndex();
+ bool camera_found = false;
+ const QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
+ for (const QCameraInfo& cameraInfo : cameras) {
+ if (input_devices[index] == cameraInfo.deviceName().toStdString() ||
+ input_devices[index] == "Auto") {
+ LOG_INFO(Frontend, "Selected Camera {} {}", cameraInfo.description().toStdString(),
+ cameraInfo.deviceName().toStdString());
+ camera = std::make_unique<QCamera>(cameraInfo);
+ if (!camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder) &&
+ !camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) {
+ LOG_ERROR(Frontend,
+ "Camera doesn't support CaptureViewfinder or CaptureStillImage");
+ continue;
+ }
+ camera_found = true;
+ break;
+ }
+ }
+
+ // Clear previous frame
+ auto blank_image = QImage(320, 240, QImage::Format::Format_RGB32);
+ blank_image.fill(Qt::black);
+ DisplayCapturedFrame(0, blank_image);
+
+ if (!camera_found) {
+ return;
+ }
+
+ camera_capture = std::make_unique<QCameraImageCapture>(camera.get());
+
+ if (!camera_capture->isCaptureDestinationSupported(
+ QCameraImageCapture::CaptureDestination::CaptureToBuffer)) {
+ LOG_ERROR(Frontend, "Camera doesn't support saving to buffer");
+ return;
+ }
+
+ camera_capture->setCaptureDestination(QCameraImageCapture::CaptureDestination::CaptureToBuffer);
+ connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this,
+ &ConfigureCamera::DisplayCapturedFrame);
+ camera->unload();
+ if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder)) {
+ camera->setCaptureMode(QCamera::CaptureViewfinder);
+ } else if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) {
+ camera->setCaptureMode(QCamera::CaptureStillImage);
+ }
+ camera->load();
+ camera->start();
+
+ pending_snapshots = 0;
+ is_virtual_camera = false;
+
+ camera_timer = std::make_unique<QTimer>();
+ connect(camera_timer.get(), &QTimer::timeout, [this] {
+ // If the camera doesn't capture, test for virtual cameras
+ if (pending_snapshots > 5) {
+ is_virtual_camera = true;
+ }
+ // Virtual cameras like obs need to reset the camera every capture
+ if (is_virtual_camera) {
+ camera->stop();
+ camera->start();
+ }
+ pending_snapshots++;
+ camera_capture->capture();
+ });
+
+ camera_timer->start(250);
+}
+
+void ConfigureCamera::DisplayCapturedFrame(int requestId, const QImage& img) {
+ LOG_INFO(Frontend, "ImageCaptured {} {}", img.width(), img.height());
+ const auto converted = img.scaled(320, 240, Qt::AspectRatioMode::IgnoreAspectRatio,
+ Qt::TransformationMode::SmoothTransformation);
+ ui->preview_box->setPixmap(QPixmap::fromImage(converted));
+ pending_snapshots = 0;
+}
+
+void ConfigureCamera::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureCamera::RetranslateUI() {
+ ui->retranslateUi(this);
+}
+
+void ConfigureCamera::ApplyConfiguration() {
+ const auto index = ui->ir_sensor_combo_box->currentIndex();
+ Settings::values.ir_sensor_device.SetValue(input_devices[index]);
+}
+
+void ConfigureCamera::LoadConfiguration() {
+ input_devices.clear();
+ ui->ir_sensor_combo_box->clear();
+ input_devices.push_back("Auto");
+ ui->ir_sensor_combo_box->addItem(tr("Auto"));
+ const auto cameras = QCameraInfo::availableCameras();
+ for (const QCameraInfo& cameraInfo : cameras) {
+ input_devices.push_back(cameraInfo.deviceName().toStdString());
+ ui->ir_sensor_combo_box->addItem(cameraInfo.description());
+ }
+
+ const auto current_device = Settings::values.ir_sensor_device.GetValue();
+
+ const auto devices_it = std::find_if(
+ input_devices.begin(), input_devices.end(),
+ [current_device](const std::string& device) { return device == current_device; });
+ const int device_index =
+ devices_it != input_devices.end()
+ ? static_cast<int>(std::distance(input_devices.begin(), devices_it))
+ : 0;
+ ui->ir_sensor_combo_box->setCurrentIndex(device_index);
+}
+
+void ConfigureCamera::RestoreDefaults() {
+ ui->ir_sensor_combo_box->setCurrentIndex(0);
+}
diff --git a/src/yuzu/configuration/configure_camera.h b/src/yuzu/configuration/configure_camera.h
new file mode 100644
index 000000000..db9833b5c
--- /dev/null
+++ b/src/yuzu/configuration/configure_camera.h
@@ -0,0 +1,54 @@
+// Text : Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+
+class QTimer;
+class QCamera;
+class QCameraImageCapture;
+
+namespace InputCommon {
+class InputSubsystem;
+} // namespace InputCommon
+
+namespace Ui {
+class ConfigureCamera;
+}
+
+class ConfigureCamera : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureCamera(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);
+ ~ConfigureCamera() override;
+
+ void ApplyConfiguration();
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ /// Load configuration settings.
+ void LoadConfiguration();
+
+ /// Restore all buttons to their default values.
+ void RestoreDefaults();
+
+ void DisplayCapturedFrame(int requestId, const QImage& img);
+
+ /// Loads and signals the current selected camera to display a frame
+ void PreviewCamera();
+
+ InputCommon::InputSubsystem* input_subsystem;
+
+ bool is_virtual_camera;
+ int pending_snapshots;
+ std::unique_ptr<QCamera> camera;
+ std::unique_ptr<QCameraImageCapture> camera_capture;
+ std::unique_ptr<QTimer> camera_timer;
+ std::vector<std::string> input_devices;
+ std::unique_ptr<Ui::ConfigureCamera> ui;
+};
diff --git a/src/yuzu/configuration/configure_camera.ui b/src/yuzu/configuration/configure_camera.ui
new file mode 100644
index 000000000..976a9b1ec
--- /dev/null
+++ b/src/yuzu/configuration/configure_camera.ui
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureCamera</class>
+ <widget class="QDialog" name="ConfigureCamera">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>298</width>
+ <height>339</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configure Infrared Camera</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>Select where the image of the emulated camera comes from. It may be a virtual camera or a real camera.</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>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="gridGroupBox">
+ <property name="title">
+ <string>Camera Image Source:</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Input device:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QComboBox" name="ir_sensor_combo_box"/>
+ </item>
+ <item row="0" column="3">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item><item>
+ <widget class="QGroupBox" name="previewBox">
+ <property name="title">
+ <string>Preview</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QLabel" name="preview_box">
+ <property name="minimumSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Resolution: 320*240</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="preview_button">
+ <property name="text">
+ <string>Click to preview</string>
+ </property>
+ </widget>
+ </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>ConfigureCamera</receiver>
+ <slot>accept()</slot>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigureCamera</receiver>
+ <slot>reject()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp
index f66cab5d4..3d69fb03f 100644
--- a/src/yuzu/configuration/configure_cpu.cpp
+++ b/src/yuzu/configuration/configure_cpu.cpp
@@ -1,12 +1,7 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <QComboBox>
-#include <QMessageBox>
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_types.h"
-#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "ui_configure_cpu.h"
@@ -36,6 +31,7 @@ void ConfigureCpu::SetConfiguration() {
ui->cpuopt_unsafe_ignore_standard_fpcr->setEnabled(runtime_lock);
ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock);
ui->cpuopt_unsafe_fastmem_check->setEnabled(runtime_lock);
+ ui->cpuopt_unsafe_ignore_global_monitor->setEnabled(runtime_lock);
ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma.GetValue());
ui->cpuopt_unsafe_reduce_fp_error->setChecked(
@@ -46,6 +42,8 @@ void ConfigureCpu::SetConfiguration() {
Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue());
ui->cpuopt_unsafe_fastmem_check->setChecked(
Settings::values.cpuopt_unsafe_fastmem_check.GetValue());
+ ui->cpuopt_unsafe_ignore_global_monitor->setChecked(
+ Settings::values.cpuopt_unsafe_ignore_global_monitor.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy.GetValue()));
@@ -82,6 +80,9 @@ void ConfigureCpu::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_fastmem_check,
ui->cpuopt_unsafe_fastmem_check,
cpuopt_unsafe_fastmem_check);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_ignore_global_monitor,
+ ui->cpuopt_unsafe_ignore_global_monitor,
+ cpuopt_unsafe_ignore_global_monitor);
}
void ConfigureCpu::changeEvent(QEvent* event) {
@@ -120,4 +121,7 @@ void ConfigureCpu::SetupPerGameUI() {
ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_fastmem_check,
Settings::values.cpuopt_unsafe_fastmem_check,
cpuopt_unsafe_fastmem_check);
+ ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_ignore_global_monitor,
+ Settings::values.cpuopt_unsafe_ignore_global_monitor,
+ cpuopt_unsafe_ignore_global_monitor);
}
diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h
index ed9af0e9f..86d928ca3 100644
--- a/src/yuzu/configuration/configure_cpu.h
+++ b/src/yuzu/configuration/configure_cpu.h
@@ -1,12 +1,10 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <QWidget>
-#include "common/settings.h"
namespace Core {
class System;
@@ -45,6 +43,7 @@ private:
ConfigurationShared::CheckState cpuopt_unsafe_ignore_standard_fpcr;
ConfigurationShared::CheckState cpuopt_unsafe_inaccurate_nan;
ConfigurationShared::CheckState cpuopt_unsafe_fastmem_check;
+ ConfigurationShared::CheckState cpuopt_unsafe_ignore_global_monitor;
const Core::System& system;
};
diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui
index d8064db24..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>
@@ -150,6 +155,18 @@
</property>
</widget>
</item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_unsafe_ignore_global_monitor">
+ <property name="toolTip">
+ <string>
+ &lt;div&gt;This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions.&lt;/div&gt;
+ </string>
+ </property>
+ <property name="text">
+ <string>Ignore global monitor</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_cpu_debug.cpp b/src/yuzu/configuration/configure_cpu_debug.cpp
index 05a90963d..3c302ec16 100644
--- a/src/yuzu/configuration/configure_cpu_debug.cpp
+++ b/src/yuzu/configuration/configure_cpu_debug.cpp
@@ -1,11 +1,6 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include <QComboBox>
-
-#include "common/common_types.h"
-#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "ui_configure_cpu_debug.h"
@@ -44,6 +39,12 @@ void ConfigureCpuDebug::SetConfiguration() {
Settings::values.cpuopt_reduce_misalign_checks.GetValue());
ui->cpuopt_fastmem->setEnabled(runtime_lock);
ui->cpuopt_fastmem->setChecked(Settings::values.cpuopt_fastmem.GetValue());
+ ui->cpuopt_fastmem_exclusives->setEnabled(runtime_lock);
+ ui->cpuopt_fastmem_exclusives->setChecked(
+ Settings::values.cpuopt_fastmem_exclusives.GetValue());
+ ui->cpuopt_recompile_exclusives->setEnabled(runtime_lock);
+ ui->cpuopt_recompile_exclusives->setChecked(
+ Settings::values.cpuopt_recompile_exclusives.GetValue());
}
void ConfigureCpuDebug::ApplyConfiguration() {
@@ -56,6 +57,8 @@ void ConfigureCpuDebug::ApplyConfiguration() {
Settings::values.cpuopt_misc_ir = ui->cpuopt_misc_ir->isChecked();
Settings::values.cpuopt_reduce_misalign_checks = ui->cpuopt_reduce_misalign_checks->isChecked();
Settings::values.cpuopt_fastmem = ui->cpuopt_fastmem->isChecked();
+ Settings::values.cpuopt_fastmem_exclusives = ui->cpuopt_fastmem_exclusives->isChecked();
+ Settings::values.cpuopt_recompile_exclusives = ui->cpuopt_recompile_exclusives->isChecked();
}
void ConfigureCpuDebug::changeEvent(QEvent* event) {
diff --git a/src/yuzu/configuration/configure_cpu_debug.h b/src/yuzu/configuration/configure_cpu_debug.h
index d06c4c63f..566ae7ecc 100644
--- a/src/yuzu/configuration/configure_cpu_debug.h
+++ b/src/yuzu/configuration/configure_cpu_debug.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_cpu_debug.ui b/src/yuzu/configuration/configure_cpu_debug.ui
index 6e635bb2f..2bc268810 100644
--- a/src/yuzu/configuration/configure_cpu_debug.ui
+++ b/src/yuzu/configuration/configure_cpu_debug.ui
@@ -144,7 +144,34 @@
</string>
</property>
<property name="text">
- <string>Enable Host MMU Emulation</string>
+ <string>Enable Host MMU Emulation (general memory instructions)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_fastmem_exclusives">
+ <property name="toolTip">
+ <string>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up exclusive memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it causes guest exclusive memory reads/writes to be done directly into memory and make use of Host's MMU.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all exclusive memory accesses to use Software MMU Emulation.&lt;/div&gt;
+ </string>
+ </property>
+ <property name="text">
+ <string>Enable Host MMU Emulation (exclusive memory instructions)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_recompile_exclusives">
+ <property name="toolTip">
+ <string>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up exclusive memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it reduces the overhead of fastmem failure of exclusive memory accesses.&lt;/div&gt;
+ </string>
+ </property>
+ <property name="text">
+ <string>Enable recompilation of exclusive memory instructions</string>
</property>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index c1cf4050c..dacc75a20 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -1,8 +1,8 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QDesktopServices>
+#include <QMessageBox>
#include <QUrl>
#include "common/fs/path_util.h"
#include "common/logging/backend.h"
@@ -15,7 +15,7 @@
#include "yuzu/uisettings.h"
ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureDebug>()}, system{system_} {
+ : QScrollArea(parent), ui{std::make_unique<Ui::ConfigureDebug>()}, system{system_} {
ui->setupUi(this);
SetConfiguration();
@@ -24,13 +24,28 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent)
QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LogDir));
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
});
+
+ connect(ui->toggle_gdbstub, &QCheckBox::toggled,
+ [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); });
+
+ connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) {
+ if (crash_dump_warning_shown) {
+ return;
+ }
+ QMessageBox::warning(this, tr("Restart Required"),
+ tr("yuzu is required to restart in order to apply this setting."),
+ QMessageBox::Ok, QMessageBox::Ok);
+ crash_dump_warning_shown = true;
+ });
}
ConfigureDebug::~ConfigureDebug() = default;
void ConfigureDebug::SetConfiguration() {
const bool runtime_lock = !system.IsPoweredOn();
-
+ ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub.GetValue());
+ ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub.GetValue());
+ ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port.GetValue());
ui->toggle_console->setEnabled(runtime_lock);
ui->toggle_console->setChecked(UISettings::values.show_console.GetValue());
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue()));
@@ -39,6 +54,7 @@ void ConfigureDebug::SetConfiguration() {
ui->fs_access_log->setEnabled(runtime_lock);
ui->fs_access_log->setChecked(Settings::values.enable_fs_access_log.GetValue());
ui->reporting_services->setChecked(Settings::values.reporting_services.GetValue());
+ ui->dump_audio_commands->setChecked(Settings::values.dump_audio_commands.GetValue());
ui->quest_flag->setChecked(Settings::values.quest_flag.GetValue());
ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue());
ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue());
@@ -53,20 +69,41 @@ void ConfigureDebug::SetConfiguration() {
ui->enable_nsight_aftermath->setChecked(Settings::values.enable_nsight_aftermath.GetValue());
ui->dump_shaders->setEnabled(runtime_lock);
ui->dump_shaders->setChecked(Settings::values.dump_shaders.GetValue());
+ ui->dump_macros->setEnabled(runtime_lock);
+ ui->dump_macros->setChecked(Settings::values.dump_macros.GetValue());
ui->disable_macro_jit->setEnabled(runtime_lock);
ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit.GetValue());
ui->disable_loop_safety_checks->setEnabled(runtime_lock);
ui->disable_loop_safety_checks->setChecked(
Settings::values.disable_shader_loop_safety_checks.GetValue());
ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue());
+ ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.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(tr("Web applet not compiled"));
+#endif
+
+#ifdef YUZU_DBGHELP
+ ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue());
+#else
+ ui->create_crash_dumps->setEnabled(false);
+ ui->create_crash_dumps->setText(tr("MiniDump creation not compiled"));
+#endif
}
void ConfigureDebug::ApplyConfiguration() {
+ Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked();
+ Settings::values.gdbstub_port = ui->gdbport_spinbox->value();
UISettings::values.show_console = ui->toggle_console->isChecked();
Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked();
Settings::values.reporting_services = ui->reporting_services->isChecked();
+ Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked();
+ Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked();
Settings::values.quest_flag = ui->quest_flag->isChecked();
Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked();
Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
@@ -76,10 +113,13 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked();
Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked();
Settings::values.dump_shaders = ui->dump_shaders->isChecked();
+ Settings::values.dump_macros = ui->dump_macros->isChecked();
Settings::values.disable_shader_loop_safety_checks =
ui->disable_loop_safety_checks->isChecked();
Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked();
Settings::values.extended_logging = ui->extended_logging->isChecked();
+ Settings::values.perform_vulkan_check = ui->perform_vulkan_check->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.h b/src/yuzu/configuration/configure_debug.h
index 73f71c9e3..030a0b7f7 100644
--- a/src/yuzu/configuration/configure_debug.h
+++ b/src/yuzu/configuration/configure_debug.h
@@ -1,11 +1,10 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
-#include <QWidget>
+#include <QScrollArea>
namespace Core {
class System;
@@ -15,7 +14,7 @@ namespace Ui {
class ConfigureDebug;
}
-class ConfigureDebug : public QWidget {
+class ConfigureDebug : public QScrollArea {
Q_OBJECT
public:
@@ -33,4 +32,6 @@ private:
std::unique_ptr<Ui::ConfigureDebug> ui;
const Core::System& system;
+
+ bool crash_dump_warning_shown{false};
};
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 4dd870855..102c8c66c 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -1,56 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureDebug</class>
- <widget class="QWidget" name="ConfigureDebug">
+ <widget class="QScrollArea" name="ConfigureDebug">
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget">
<layout class="QVBoxLayout" name="verticalLayout_1">
<item>
- <widget class="QGroupBox" name="groupBox_2">
- <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">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Debugger</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
- <widget class="QLabel" name="label_1">
- <property name="text">
- <string>Global Log Filter</string>
- </property>
+ <widget class="QCheckBox" name="toggle_gdbstub">
+ <property name="text">
+ <string>Enable GDB Stub</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">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_11">
<property name="text">
- <string>Open Log Location</string>
+ <string>Port:</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>
+ <item>
+ <widget class="QSpinBox" name="gdbport_spinbox">
+ <property name="minimum">
+ <number>1024</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <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>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>
@@ -118,6 +176,19 @@
</property>
</widget>
</item>
+ <item row="0" column="2">
+ <widget class="QCheckBox" name="dump_macros">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, it will dump all the macro programs of the GPU</string>
+ </property>
+ <property name="text">
+ <string>Dump Maxwell Macros</string>
+ </property>
+ </widget>
+ </item>
<item row="0" column="1">
<widget class="QCheckBox" name="disable_macro_jit">
<property name="enabled">
@@ -160,6 +231,13 @@
<string>Debugging</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="reporting_services">
+ <property name="text">
+ <string>Enable Verbose Reporting Services**</string>
+ </property>
+ </widget>
+ </item>
<item row="0" column="0">
<widget class="QCheckBox" name="fs_access_log">
<property name="text">
@@ -167,10 +245,20 @@
</property>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QCheckBox" name="reporting_services">
+ <item row="0" column="1">
+ <widget class="QCheckBox" name="dump_audio_commands">
+ <property name="toolTip">
+ <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string>
+ </property>
<property name="text">
- <string>Enable Verbose Reporting Services**</string>
+ <string>Dump Audio Commands To Console**</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QCheckBox" name="create_crash_dumps">
+ <property name="text">
+ <string>Create Minidump After Crash</string>
</property>
</widget>
</item>
@@ -183,7 +271,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 +302,24 @@
<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>
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="perform_vulkan_check">
+ <property name="toolTip">
+ <string>Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu.</string>
+ </property>
+ <property name="text">
+ <string>Perform Startup Vulkan Check</string>
</property>
</widget>
</item>
@@ -238,6 +343,7 @@
</item>
</layout>
</widget>
+ </widget>
<tabstops>
<tabstop>log_filter_edit</tabstop>
<tabstop>toggle_console</tabstop>
diff --git a/src/yuzu/configuration/configure_debug_controller.cpp b/src/yuzu/configuration/configure_debug_controller.cpp
index 9a8de92a1..42abe9119 100644
--- a/src/yuzu/configuration/configure_debug_controller.cpp
+++ b/src/yuzu/configuration/configure_debug_controller.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hid/hid_core.h"
#include "ui_configure_debug_controller.h"
diff --git a/src/yuzu/configuration/configure_debug_controller.h b/src/yuzu/configuration/configure_debug_controller.h
index d716edbc2..aaed717e2 100644
--- a/src/yuzu/configuration/configure_debug_controller.h
+++ b/src/yuzu/configuration/configure_debug_controller.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_debug_tab.cpp b/src/yuzu/configuration/configure_debug_tab.cpp
index e69cca1ef..d1ca4752a 100644
--- a/src/yuzu/configuration/configure_debug_tab.cpp
+++ b/src/yuzu/configuration/configure_debug_tab.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include "ui_configure_debug_tab.h"
diff --git a/src/yuzu/configuration/configure_debug_tab.h b/src/yuzu/configuration/configure_debug_tab.h
index 4f68260aa..c0fd9f73f 100644
--- a/src/yuzu/configuration/configure_debug_tab.h
+++ b/src/yuzu/configuration/configure_debug_tab.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 19133ccf5..4301313cf 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -1,15 +1,7 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
-#include <QAbstractButton>
-#include <QDialogButtonBox>
-#include <QHash>
-#include <QListWidgetItem>
-#include <QPushButton>
-#include <QSignalBlocker>
-#include <QTabWidget>
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
@@ -32,13 +24,14 @@
#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,
+ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
InputCommon::InputSubsystem* input_subsystem,
- Core::System& system_)
+ Core::System& system_, bool enable_web_config)
: QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()},
- registry(registry), system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_,
- this)},
+ registry(registry_), system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_,
+ this)},
cpu_tab{std::make_unique<ConfigureCpu>(system_, this)},
debug_tab_tab{std::make_unique<ConfigureDebugTab>(system_, this)},
filesystem_tab{std::make_unique<ConfigureFilesystem>(this)},
@@ -71,6 +64,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
ui->tabWidget->addTab(ui_tab.get(), tr("Game List"));
ui->tabWidget->addTab(web_tab.get(), tr("Web"));
+ web_tab->SetWebServiceConfigEnabled(enable_web_config);
hotkeys_tab->Populate(registry);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@@ -176,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_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 32ddfd4e0..1f724834a 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -40,8 +39,9 @@ class ConfigureDialog : public QDialog {
Q_OBJECT
public:
- explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
- InputCommon::InputSubsystem* input_subsystem, Core::System& system_);
+ explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
+ InputCommon::InputSubsystem* input_subsystem, Core::System& system_,
+ bool enable_web_config = true);
~ConfigureDialog() override;
void ApplyConfiguration();
diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp
index d6fb43f8b..ad1951754 100644
--- a/src/yuzu/configuration/configure_filesystem.cpp
+++ b/src/yuzu/configuration/configure_filesystem.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QFileDialog>
#include <QMessageBox>
diff --git a/src/yuzu/configuration/configure_filesystem.h b/src/yuzu/configuration/configure_filesystem.h
index b4f9355eb..31d2f1d56 100644
--- a/src/yuzu/configuration/configure_filesystem.h
+++ b/src/yuzu/configuration/configure_filesystem.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 978a29fe6..7ade01ba6 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -1,16 +1,12 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <functional>
#include <utility>
-#include <QCheckBox>
#include <QMessageBox>
-#include <QSpinBox>
#include "common/settings.h"
#include "core/core.h"
#include "ui_configure_general.h"
-#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_general.h"
#include "yuzu/uisettings.h"
@@ -30,9 +26,6 @@ ConfigureGeneral::ConfigureGeneral(const Core::System& system_, QWidget* parent)
connect(ui->button_reset_defaults, &QPushButton::clicked, this,
&ConfigureGeneral::ResetDefaults);
-
- ui->fps_cap_label->setVisible(Settings::IsConfiguringGlobal());
- ui->fps_cap_combobox->setVisible(!Settings::IsConfiguringGlobal());
}
ConfigureGeneral::~ConfigureGeneral() = default;
@@ -42,6 +35,9 @@ void ConfigureGeneral::SetConfiguration() {
ui->use_multi_core->setEnabled(runtime_lock);
ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue());
+ ui->use_extended_memory_layout->setEnabled(runtime_lock);
+ ui->use_extended_memory_layout->setChecked(
+ Settings::values.use_extended_memory_layout.GetValue());
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing.GetValue());
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot.GetValue());
@@ -52,8 +48,6 @@ void ConfigureGeneral::SetConfiguration() {
ui->toggle_speed_limit->setChecked(Settings::values.use_speed_limit.GetValue());
ui->speed_limit->setValue(Settings::values.speed_limit.GetValue());
- ui->fps_cap->setValue(Settings::values.fps_cap.GetValue());
-
ui->button_reset_defaults->setEnabled(runtime_lock);
if (Settings::IsConfiguringGlobal()) {
@@ -61,11 +55,6 @@ void ConfigureGeneral::SetConfiguration() {
} else {
ui->speed_limit->setEnabled(Settings::values.use_speed_limit.GetValue() &&
use_speed_limit != ConfigurationShared::CheckState::Global);
-
- ui->fps_cap_combobox->setCurrentIndex(Settings::values.fps_cap.UsingGlobal() ? 0 : 1);
- ui->fps_cap->setEnabled(!Settings::values.fps_cap.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->fps_cap_layout,
- !Settings::values.fps_cap.UsingGlobal());
}
}
@@ -91,6 +80,9 @@ void ConfigureGeneral::ResetDefaults() {
void ConfigureGeneral::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, ui->use_multi_core,
use_multi_core);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_extended_memory_layout,
+ ui->use_extended_memory_layout,
+ use_extended_memory_layout);
if (Settings::IsConfiguringGlobal()) {
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
@@ -99,8 +91,6 @@ void ConfigureGeneral::ApplyConfiguration() {
UISettings::values.mute_when_in_background = ui->toggle_background_mute->isChecked();
UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked();
- Settings::values.fps_cap.SetValue(ui->fps_cap->value());
-
// Guard if during game and set to game-specific value
if (Settings::values.use_speed_limit.UsingGlobal()) {
Settings::values.use_speed_limit.SetValue(ui->toggle_speed_limit->checkState() ==
@@ -116,13 +106,6 @@ void ConfigureGeneral::ApplyConfiguration() {
Qt::Checked);
Settings::values.speed_limit.SetValue(ui->speed_limit->value());
}
-
- if (ui->fps_cap_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.fps_cap.SetGlobal(true);
- } else {
- Settings::values.fps_cap.SetGlobal(false);
- Settings::values.fps_cap.SetValue(ui->fps_cap->value());
- }
}
}
@@ -160,14 +143,12 @@ void ConfigureGeneral::SetupPerGameUI() {
Settings::values.use_speed_limit, use_speed_limit);
ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core,
use_multi_core);
+ ConfigurationShared::SetColoredTristate(ui->use_extended_memory_layout,
+ Settings::values.use_extended_memory_layout,
+ use_extended_memory_layout);
connect(ui->toggle_speed_limit, &QCheckBox::clicked, ui->speed_limit, [this]() {
ui->speed_limit->setEnabled(ui->toggle_speed_limit->isChecked() &&
(use_speed_limit != ConfigurationShared::CheckState::Global));
});
-
- connect(ui->fps_cap_combobox, qOverload<int>(&QComboBox::activated), this, [this](int index) {
- ui->fps_cap->setEnabled(index == 1);
- ConfigurationShared::SetHighlight(ui->fps_cap_layout, index == 1);
- });
}
diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h
index 85c1dd4a8..a090c1a3f 100644
--- a/src/yuzu/configuration/configure_general.h
+++ b/src/yuzu/configuration/configure_general.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -48,6 +47,7 @@ private:
ConfigurationShared::CheckState use_speed_limit;
ConfigurationShared::CheckState use_multi_core;
+ ConfigurationShared::CheckState use_extended_memory_layout;
const Core::System& system;
};
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index bfc771135..5b90b1109 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -28,87 +28,6 @@
<item>
<layout class="QVBoxLayout" name="GeneralVerticalLayout">
<item>
- <widget class="QWidget" name="fps_cap_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_4">
- <item>
- <widget class="QComboBox" name="fps_cap_combobox">
- <property name="currentText">
- <string>Use global framerate cap</string>
- </property>
- <property name="currentIndex">
- <number>0</number>
- </property>
- <item>
- <property name="text">
- <string>Use global framerate cap</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Set framerate cap:</string>
- </property>
- </item>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="fps_cap_label">
- <property name="toolTip">
- <string>Requires the use of the FPS Limiter Toggle hotkey to take effect.</string>
- </property>
- <property name="text">
- <string>Framerate Cap</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QSpinBox" name="fps_cap">
- <property name="suffix">
- <string>x</string>
- </property>
- <property name="minimum">
- <number>1</number>
- </property>
- <property name="maximum">
- <number>1000</number>
- </property>
- <property name="value">
- <number>500</number>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="toggle_speed_limit">
@@ -143,6 +62,13 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="use_extended_memory_layout">
+ <property name="text">
+ <string>Extended memory layout (6GB DRAM)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QCheckBox" name="toggle_check_exit">
<property name="text">
<string>Confirm exit while emulation is running</string>
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 59f975a6e..87e5d0f48 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -1,12 +1,10 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
// Include this early to include Vulkan headers how we want to
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include <QColorDialog>
-#include <QComboBox>
#include <QVulkanInstance>
#include "common/common_types.h"
@@ -18,6 +16,7 @@
#include "video_core/vulkan_common/vulkan_library.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_graphics.h"
+#include "yuzu/uisettings.h"
ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* parent)
: QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, system{system_} {
@@ -58,6 +57,9 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren
UpdateBackgroundColorButton(new_bg_color);
});
+ ui->api->setEnabled(!UISettings::values.has_broken_vulkan);
+ ui->api_widget->setEnabled(!UISettings::values.has_broken_vulkan ||
+ Settings::IsConfiguringGlobal());
ui->bg_label->setVisible(Settings::IsConfiguringGlobal());
ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal());
}
@@ -320,6 +322,10 @@ void ConfigureGraphics::UpdateAPILayout() {
}
void ConfigureGraphics::RetrieveVulkanDevices() try {
+ if (UISettings::values.has_broken_vulkan) {
+ return;
+ }
+
using namespace Vulkan;
vk::InstanceDispatch dld;
@@ -333,7 +339,6 @@ void ConfigureGraphics::RetrieveVulkanDevices() try {
const std::string name = vk::PhysicalDevice(device, dld).GetProperties().deviceName;
vulkan_devices.push_back(QString::fromStdString(name));
}
-
} catch (const Vulkan::vk::Exception& exception) {
LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what());
}
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index 1b101c940..70034eb1b 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 74f0e0b79..1e4f74704 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>437</width>
- <height>482</height>
+ <width>541</width>
+ <height>759</height>
</rect>
</property>
<property name="windowTitle">
@@ -171,11 +171,11 @@
</widget>
</item>
<item>
- <widget class="QCheckBox" name="accelerate_astc">
- <property name="text">
- <string>Accelerate ASTC texture decoding</string>
- </property>
- </widget>
+ <widget class="QCheckBox" name="accelerate_astc">
+ <property name="text">
+ <string>Accelerate ASTC texture decoding</string>
+ </property>
+ </widget>
</item>
<item>
<widget class="QWidget" name="nvdec_emulation_widget" native="true">
@@ -438,43 +438,43 @@
</widget>
</item>
<item>
- <widget class="QWidget" name="anti_aliasing_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_7">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
+ <widget class="QWidget" name="anti_aliasing_layout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="anti_aliasing_label">
+ <property name="text">
+ <string>Anti-Aliasing Method:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="anti_aliasing_combobox">
+ <item>
+ <property name="text">
+ <string>None</string>
</property>
- <property name="bottomMargin">
- <number>0</number>
+ </item>
+ <item>
+ <property name="text">
+ <string>FXAA</string>
</property>
- <item>
- <widget class="QLabel" name="anti_aliasing_label">
- <property name="text">
- <string>Anti-Aliasing Method:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="anti_aliasing_combobox">
- <item>
- <property name="text">
- <string>None</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>FXAA</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
<item>
<widget class="QWidget" name="bg_layout" native="true">
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index 30c5a3595..01f074699 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h"
#include "core/core.h"
@@ -29,6 +28,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {
ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue());
ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue());
ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue());
+ ui->use_pessimistic_flushes->setChecked(Settings::values.use_pessimistic_flushes.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->gpu_accuracy->setCurrentIndex(
@@ -56,6 +56,8 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() {
use_asynchronous_shaders);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time,
ui->use_fast_gpu_time, use_fast_gpu_time);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_pessimistic_flushes,
+ ui->use_pessimistic_flushes, use_pessimistic_flushes);
}
void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
@@ -78,6 +80,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
ui->use_asynchronous_shaders->setEnabled(
Settings::values.use_asynchronous_shaders.UsingGlobal());
ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal());
+ ui->use_pessimistic_flushes->setEnabled(
+ Settings::values.use_pessimistic_flushes.UsingGlobal());
ui->anisotropic_filtering_combobox->setEnabled(
Settings::values.max_anisotropy.UsingGlobal());
@@ -90,6 +94,9 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
use_asynchronous_shaders);
ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time,
Settings::values.use_fast_gpu_time, use_fast_gpu_time);
+ ConfigurationShared::SetColoredTristate(ui->use_pessimistic_flushes,
+ Settings::values.use_pessimistic_flushes,
+ use_pessimistic_flushes);
ConfigurationShared::SetColoredComboBox(
ui->gpu_accuracy, ui->label_gpu_accuracy,
static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));
diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h
index 0a1724ce4..12e816905 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.h
+++ b/src/yuzu/configuration/configure_graphics_advanced.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -40,6 +39,7 @@ private:
ConfigurationShared::CheckState use_vsync;
ConfigurationShared::CheckState use_asynchronous_shaders;
ConfigurationShared::CheckState use_fast_gpu_time;
+ ConfigurationShared::CheckState use_pessimistic_flushes;
const Core::System& system;
};
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui
index 96de0b3d1..87a121471 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.ui
+++ b/src/yuzu/configuration/configure_graphics_advanced.ui
@@ -75,7 +75,7 @@
<string>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference.</string>
</property>
<property name="text">
- <string>Use VSync (OpenGL only)</string>
+ <string>Use VSync</string>
</property>
</widget>
</item>
@@ -100,6 +100,16 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="use_pessimistic_flushes">
+ <property name="toolTip">
+ <string>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</string>
+ </property>
+ <property name="text">
+ <string>Use pessimistic buffer flushes (Hack)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QWidget" name="af_layout" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_1">
<property name="leftMargin">
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
index 53e629a5e..daa77a8f8 100644
--- a/src/yuzu/configuration/configure_hotkeys.cpp
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QMenu>
#include <QMessageBox>
@@ -35,8 +34,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);
@@ -60,14 +60,18 @@ ConfigureHotkeys::~ConfigureHotkeys() = default;
void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
for (const auto& group : registry.hotkey_groups) {
- auto* parent_item = new QStandardItem(group.first);
+ auto* parent_item =
+ new QStandardItem(QCoreApplication::translate("Hotkeys", qPrintable(group.first)));
parent_item->setEditable(false);
+ parent_item->setData(group.first);
for (const auto& hotkey : group.second) {
- auto* action = new QStandardItem(hotkey.first);
+ auto* action =
+ new QStandardItem(QCoreApplication::translate("Hotkeys", qPrintable(hotkey.first)));
auto* keyseq =
new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
auto* controller_keyseq = new QStandardItem(hotkey.second.controller_keyseq);
action->setEditable(false);
+ action->setData(hotkey.first);
keyseq->setEditable(false);
controller_keyseq->setEditable(false);
parent_item->appendRow({action, keyseq, controller_keyseq});
@@ -76,8 +80,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) {
@@ -92,6 +96,16 @@ void ConfigureHotkeys::RetranslateUI() {
ui->retranslateUi(this);
model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Controller Hotkey")});
+ for (int key_id = 0; key_id < model->rowCount(); key_id++) {
+ QStandardItem* parent = model->item(key_id, 0);
+ parent->setText(
+ QCoreApplication::translate("Hotkeys", qPrintable(parent->data().toString())));
+ for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
+ QStandardItem* action = parent->child(key_column_id, name_column);
+ action->setText(
+ QCoreApplication::translate("Hotkeys", qPrintable(action->data().toString())));
+ }
+ }
}
void ConfigureHotkeys::Configure(QModelIndex index) {
@@ -272,10 +286,10 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
const QStandardItem* controller_keyseq =
parent->child(key_column_id, controller_column);
for (auto& [group, sub_actions] : registry.hotkey_groups) {
- if (group != parent->text())
+ if (group != parent->data())
continue;
for (auto& [action_name, hotkey] : sub_actions) {
- if (action_name != action->text())
+ if (action_name != action->data())
continue;
hotkey.keyseq = QKeySequence(keyseq->text());
hotkey.controller_keyseq = controller_keyseq->text();
diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h
index f943ec538..b45ecb185 100644
--- a/src/yuzu/configuration/configure_hotkeys.h
+++ b/src/yuzu/configuration/configure_hotkeys.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 7c5776189..1db374d4a 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -1,14 +1,9 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include <algorithm>
#include <memory>
#include <thread>
-#include <QSignalBlocker>
-#include <QTimer>
-
#include "core/core.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
@@ -19,11 +14,13 @@
#include "ui_configure_input.h"
#include "ui_configure_input_advanced.h"
#include "ui_configure_input_player.h"
+#include "yuzu/configuration/configure_camera.h"
#include "yuzu/configuration/configure_debug_controller.h"
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_input_advanced.h"
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_motion_touch.h"
+#include "yuzu/configuration/configure_ringcon.h"
#include "yuzu/configuration/configure_touchscreen_advanced.h"
#include "yuzu/configuration/configure_vibration.h"
#include "yuzu/configuration/input_profiles.h"
@@ -68,7 +65,7 @@ void OnDockedModeChanged(bool last_state, bool new_state, Core::System& system)
ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
- profiles(std::make_unique<InputProfiles>(system_)), system{system_} {
+ profiles(std::make_unique<InputProfiles>()), system{system_} {
ui->setupUi(this);
}
@@ -162,6 +159,13 @@ 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(advanced, &ConfigureInputAdvanced::CallCameraDialog, [this, input_subsystem] {
+ CallConfigureDialog<ConfigureCamera>(*this, input_subsystem);
+ });
connect(ui->vibrationButton, &QPushButton::clicked,
[this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index 4cafa3dab..c89189c36 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui
index 2707025e7..d51774028 100644
--- a/src/yuzu/configuration/configure_input.ui
+++ b/src/yuzu/configuration/configure_input.ui
@@ -166,7 +166,7 @@
<item>
<widget class="QRadioButton" name="radioUndocked">
<property name="text">
- <string>Undocked</string>
+ <string>Handheld</string>
</property>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index 20fc2599d..10f841b98 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QColorDialog>
#include "common/settings.h"
@@ -79,13 +78,18 @@ 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(); });
+ connect(ui->camera_configure, &QPushButton::clicked, this, [this] { CallCameraDialog(); });
#ifndef _WIN32
ui->enable_raw_input->setVisible(false);
@@ -132,6 +136,8 @@ 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();
+ Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked();
}
void ConfigureInputAdvanced::LoadConfiguration() {
@@ -164,6 +170,8 @@ 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());
+ ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue());
UpdateUIEnabled();
}
@@ -185,4 +193,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..fc1230284 100644
--- a/src/yuzu/configuration/configure_input_advanced.h
+++ b/src/yuzu/configuration/configure_input_advanced.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -29,6 +28,8 @@ signals:
void CallMouseConfigDialog();
void CallTouchscreenConfigDialog();
void CallMotionTouchConfigDialog();
+ void CallRingControllerDialog();
+ void CallCameraDialog();
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..fac8cf827 100644
--- a/src/yuzu/configuration/configure_input_advanced.ui
+++ b/src/yuzu/configuration/configure_input_advanced.ui
@@ -2603,6 +2603,34 @@
</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>
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="enable_ir_sensor">
+ <property name="text">
+ <string>Infrared Camera</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="2">
+ <widget class="QPushButton" name="camera_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 9db564663..9e5a40fe7 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -1,16 +1,15 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <memory>
#include <utility>
#include <QGridLayout>
#include <QInputDialog>
-#include <QKeyEvent>
#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"
@@ -23,7 +22,6 @@
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_input_player_widget.h"
-#include "yuzu/configuration/configure_vibration.h"
#include "yuzu/configuration/input_profiles.h"
#include "yuzu/util/limitable_input_dialog.h"
@@ -121,6 +119,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
@@ -146,6 +161,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : "");
+ const QString invert = QString::fromStdString(param.Get("invert", "+") == "-" ? "-" : "");
const auto common_button_name = input_subsystem->GetButtonName(param);
// Retrieve the names from Qt
@@ -164,12 +180,12 @@ 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")) {
const QString axis = QString::fromStdString(param.Get("axis", ""));
- return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis);
+ return QObject::tr("%1%2Axis %3").arg(toggle, invert, axis);
}
if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) {
const QString axis_x = QString::fromStdString(param.Get("axis_x", ""));
@@ -248,15 +264,16 @@ QString ConfigureInputPlayer::AnalogToText(const Common::ParamPackage& param,
return QObject::tr("[unknown]");
}
-ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index,
- QWidget* bottom_row,
+ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index_,
+ QWidget* bottom_row_,
InputCommon::InputSubsystem* input_subsystem_,
InputProfiles* profiles_, Core::HID::HIDCore& hid_core_,
- bool is_powered_on_, bool debug)
- : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
- debug(debug), is_powered_on{is_powered_on_}, input_subsystem{input_subsystem_},
- profiles(profiles_), timeout_timer(std::make_unique<QTimer>()),
- poll_timer(std::make_unique<QTimer>()), bottom_row(bottom_row), hid_core{hid_core_} {
+ bool is_powered_on_, bool debug_)
+ : QWidget(parent),
+ ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index{player_index_}, debug{debug_},
+ is_powered_on{is_powered_on_}, input_subsystem{input_subsystem_}, profiles(profiles_),
+ timeout_timer(std::make_unique<QTimer>()),
+ poll_timer(std::make_unique<QTimer>()), bottom_row{bottom_row_}, hid_core{hid_core_} {
if (player_index == 0) {
auto* emulated_controller_p1 =
hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
@@ -346,18 +363,18 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
button_map[button_id]->setText(tr("[not set]"));
});
if (param.Has("code") || param.Has("button") || param.Has("hat")) {
- context_menu.addAction(tr("Toggle button"), [&] {
- const bool toggle_value = !param.Get("toggle", false);
- param.Set("toggle", toggle_value);
- button_map[button_id]->setText(ButtonToText(param));
- emulated_controller->SetButtonParam(button_id, param);
- });
context_menu.addAction(tr("Invert button"), [&] {
const bool invert_value = !param.Get("inverted", false);
param.Set("inverted", invert_value);
button_map[button_id]->setText(ButtonToText(param));
emulated_controller->SetButtonParam(button_id, param);
});
+ context_menu.addAction(tr("Toggle button"), [&] {
+ const bool toggle_value = !param.Get("toggle", false);
+ param.Set("toggle", toggle_value);
+ button_map[button_id]->setText(ButtonToText(param));
+ emulated_controller->SetButtonParam(button_id, param);
+ });
}
if (param.Has("axis")) {
context_menu.addAction(tr("Invert axis"), [&] {
@@ -382,6 +399,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
}
emulated_controller->SetButtonParam(button_id, param);
});
+ context_menu.addAction(tr("Toggle axis"), [&] {
+ const bool toggle_value = !param.Get("toggle", false);
+ param.Set("toggle", toggle_value);
+ button_map[button_id]->setText(ButtonToText(param));
+ emulated_controller->SetButtonParam(button_id, param);
+ });
}
context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
});
@@ -473,6 +496,25 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
[=, this](const Common::ParamPackage& params) {
Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
+ // Correct axis direction for inverted sticks
+ if (input_subsystem->IsStickInverted(param)) {
+ switch (analog_id) {
+ case Settings::NativeAnalog::LStick: {
+ const bool invert_value = param.Get("invert_x", "+") == "-";
+ const std::string invert_str = invert_value ? "+" : "-";
+ param.Set("invert_x", invert_str);
+ break;
+ }
+ case Settings::NativeAnalog::RStick: {
+ const bool invert_value = param.Get("invert_y", "+") == "-";
+ const std::string invert_str = invert_value ? "+" : "-";
+ param.Set("invert_y", invert_str);
+ break;
+ }
+ default:
+ break;
+ }
+ }
emulated_controller->SetStickParam(analog_id, param);
},
InputCommon::Polling::InputType::Stick);
@@ -485,7 +527,28 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
QMenu context_menu;
Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
context_menu.addAction(tr("Clear"), [&] {
- emulated_controller->SetStickParam(analog_id, {});
+ if (param.Get("engine", "") != "analog_from_button") {
+ emulated_controller->SetStickParam(analog_id, {});
+ for (auto button : analog_map_buttons[analog_id]) {
+ button->setText(tr("[not set]"));
+ }
+ return;
+ }
+ switch (sub_button_id) {
+ case 0:
+ param.Erase("up");
+ break;
+ case 1:
+ param.Erase("down");
+ break;
+ case 2:
+ param.Erase("left");
+ break;
+ case 3:
+ param.Erase("right");
+ break;
+ }
+ emulated_controller->SetStickParam(analog_id, param);
analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
});
context_menu.addAction(tr("Center axis"), [&] {
@@ -640,39 +703,38 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
UpdateControllerEnabledButtons();
UpdateControllerButtonNames();
UpdateMotionButtons();
- connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged),
- [this, player_index](int) {
- UpdateControllerAvailableButtons();
- UpdateControllerEnabledButtons();
- UpdateControllerButtonNames();
- UpdateMotionButtons();
- const Core::HID::NpadStyleIndex type =
- GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
-
- if (player_index == 0) {
- auto* emulated_controller_p1 =
- hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
- auto* emulated_controller_handheld =
- hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
- bool is_connected = emulated_controller->IsConnected(true);
-
- emulated_controller_p1->SetNpadStyleIndex(type);
- emulated_controller_handheld->SetNpadStyleIndex(type);
- if (is_connected) {
- if (type == Core::HID::NpadStyleIndex::Handheld) {
- emulated_controller_p1->Disconnect();
- emulated_controller_handheld->Connect(true);
- emulated_controller = emulated_controller_handheld;
- } else {
- emulated_controller_handheld->Disconnect();
- emulated_controller_p1->Connect(true);
- emulated_controller = emulated_controller_p1;
- }
- }
- ui->controllerFrame->SetController(emulated_controller);
+ connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {
+ UpdateControllerAvailableButtons();
+ UpdateControllerEnabledButtons();
+ UpdateControllerButtonNames();
+ UpdateMotionButtons();
+ const Core::HID::NpadStyleIndex type =
+ GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+
+ if (player_index == 0) {
+ auto* emulated_controller_p1 =
+ hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
+ auto* emulated_controller_handheld =
+ hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
+ bool is_connected = emulated_controller->IsConnected(true);
+
+ emulated_controller_p1->SetNpadStyleIndex(type);
+ emulated_controller_handheld->SetNpadStyleIndex(type);
+ if (is_connected) {
+ if (type == Core::HID::NpadStyleIndex::Handheld) {
+ emulated_controller_p1->Disconnect();
+ emulated_controller_handheld->Connect(true);
+ emulated_controller = emulated_controller_handheld;
+ } else {
+ emulated_controller_handheld->Disconnect();
+ emulated_controller_p1->Connect(true);
+ emulated_controller = emulated_controller_p1;
}
- emulated_controller->SetNpadStyleIndex(type);
- });
+ }
+ ui->controllerFrame->SetController(emulated_controller);
+ }
+ emulated_controller->SetNpadStyleIndex(type);
+ });
connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this,
&ConfigureInputPlayer::UpdateMappingWithDefaults);
@@ -953,7 +1015,7 @@ void ConfigureInputPlayer::UpdateUI() {
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);
- range_spinbox->setValue(static_cast<int>(param.Get("range", 1.0f) * 100));
+ range_spinbox->setValue(static_cast<int>(param.Get("range", 0.95f) * 100));
} else {
slider_value = static_cast<int>(param.Get("modifier_scale", 0.5f) * 100);
modifier_label->setText(tr("Modifier Range: %1%").arg(slider_value));
@@ -1332,6 +1394,9 @@ void ConfigureInputPlayer::HandleClick(
QPushButton* button, std::size_t button_id,
std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::InputType type) {
+ if (timeout_timer->isActive()) {
+ return;
+ }
if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) {
button->setText(tr("Shake!"));
} else {
@@ -1352,7 +1417,7 @@ void ConfigureInputPlayer::HandleClick(
ui->controllerFrame->BeginMappingAnalog(button_id);
}
- timeout_timer->start(2500); // Cancel after 2.5 seconds
+ timeout_timer->start(4000); // Cancel after 4 seconds
poll_timer->start(25); // Check for new inputs every 25ms
}
@@ -1406,10 +1471,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());
}
@@ -1417,7 +1482,7 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
void ConfigureInputPlayer::CreateProfile() {
const auto profile_name =
- LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20,
+ LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 30,
LimitableInputDialog::InputLimiter::Filesystem);
if (profile_name.isEmpty()) {
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index 47df6b3d3..79434fdd8 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui
index 756a414b5..a62b57501 100644
--- a/src/yuzu/configuration/configure_input_player.ui
+++ b/src/yuzu/configuration/configure_input_player.ui
@@ -754,13 +754,13 @@
<string>%</string>
</property>
<property name="minimum">
- <number>50</number>
+ <number>25</number>
</property>
<property name="maximum">
<number>150</number>
</property>
<property name="value">
- <number>100</number>
+ <number>95</number>
</property>
</widget>
</item>
@@ -2985,13 +2985,13 @@
<string>%</string>
</property>
<property name="minimum">
- <number>50</number>
+ <number>25</number>
</property>
<property name="maximum">
<number>150</number>
</property>
<property name="value">
- <number>100</number>
+ <number>95</number>
</property>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index fb168b2ca..11390fec0 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <QMenu>
diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h
index 3582ef77a..b258c6d77 100644
--- a/src/yuzu/configuration/configure_input_player_widget.h
+++ b/src/yuzu/configuration/configure_input_player_widget.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_input_profile_dialog.cpp b/src/yuzu/configuration/configure_input_profile_dialog.cpp
index 17bbe6b61..58dffda51 100644
--- a/src/yuzu/configuration/configure_input_profile_dialog.cpp
+++ b/src/yuzu/configuration/configure_input_profile_dialog.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "ui_configure_input_profile_dialog.h"
diff --git a/src/yuzu/configuration/configure_input_profile_dialog.h b/src/yuzu/configuration/configure_input_profile_dialog.h
index 84b1f6d1a..956cdf954 100644
--- a/src/yuzu/configuration/configure_input_profile_dialog.h
+++ b/src/yuzu/configuration/configure_input_profile_dialog.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp
index 4340de304..d1b870c72 100644
--- a/src/yuzu/configuration/configure_motion_touch.cpp
+++ b/src/yuzu/configuration/configure_motion_touch.cpp
@@ -1,17 +1,11 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include <array>
#include <sstream>
#include <QCloseEvent>
-#include <QLabel>
#include <QMessageBox>
-#include <QPushButton>
-#include <QRegularExpression>
#include <QStringListModel>
-#include <QVBoxLayout>
#include "common/logging/log.h"
#include "common/settings.h"
@@ -156,6 +150,8 @@ void ConfigureMotionTouch::ConnectEvents() {
&ConfigureMotionTouch::OnConfigureTouchCalibration);
connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
&ConfigureMotionTouch::OnConfigureTouchFromButton);
+ connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
+ &ConfigureMotionTouch::ApplyConfiguration);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
if (CanCloseDialog()) {
reject();
diff --git a/src/yuzu/configuration/configure_motion_touch.h b/src/yuzu/configuration/configure_motion_touch.h
index 8b707d2ff..7dcc9318e 100644
--- a/src/yuzu/configuration/configure_motion_touch.h
+++ b/src/yuzu/configuration/configure_motion_touch.h
@@ -1,12 +1,10 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <QDialog>
-#include "common/param_package.h"
class QLabel;
class QPushButton;
diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui
index c75a84ae4..0237fae54 100644
--- a/src/yuzu/configuration/configure_motion_touch.ui
+++ b/src/yuzu/configuration/configure_motion_touch.ui
@@ -293,22 +293,5 @@
</layout>
</widget>
<resources/>
- <connections>
- <connection>
- <sender>buttonBox</sender>
- <signal>accepted()</signal>
- <receiver>ConfigureMotionTouch</receiver>
- <slot>ApplyConfiguration()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>20</x>
- <y>20</y>
- </hint>
- <hint type="destinationlabel">
- <x>20</x>
- <y>20</y>
- </hint>
- </hints>
- </connection>
- </connections>
+ <connections/>
</ui>
diff --git a/src/yuzu/configuration/configure_network.cpp b/src/yuzu/configuration/configure_network.cpp
index 7020d2964..ba1986eb1 100644
--- a/src/yuzu/configuration/configure_network.cpp
+++ b/src/yuzu/configuration/configure_network.cpp
@@ -1,12 +1,10 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include <QGraphicsItem>
#include <QtConcurrent/QtConcurrent>
#include "common/settings.h"
#include "core/core.h"
-#include "core/network/network_interface.h"
+#include "core/internal_network/network_interface.h"
#include "ui_configure_network.h"
#include "yuzu/configuration/configure_network.h"
@@ -28,7 +26,15 @@ void ConfigureNetwork::ApplyConfiguration() {
Settings::values.network_interface = ui->network_interface->currentText().toStdString();
}
-void ConfigureNetwork::RetranslateUi() {
+void ConfigureNetwork::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureNetwork::RetranslateUI() {
ui->retranslateUi(this);
}
diff --git a/src/yuzu/configuration/configure_network.h b/src/yuzu/configuration/configure_network.h
index 8507c62eb..f666edbd1 100644
--- a/src/yuzu/configuration/configure_network.h
+++ b/src/yuzu/configuration/configure_network.h
@@ -1,11 +1,9 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
-#include <QFutureWatcher>
#include <QWidget>
namespace Ui {
@@ -20,9 +18,10 @@ public:
~ConfigureNetwork() override;
void ApplyConfiguration();
- void RetranslateUi();
private:
+ void changeEvent(QEvent*) override;
+ void RetranslateUI();
void SetConfiguration();
std::unique_ptr<Ui::ConfigureNetwork> ui;
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index f4cf25f05..c3cb8f61d 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <filesystem>
@@ -12,17 +11,11 @@
#include <QAbstractButton>
#include <QCheckBox>
-#include <QDialogButtonBox>
-#include <QHeaderView>
-#include <QMenu>
#include <QPushButton>
-#include <QStandardItemModel>
#include <QString>
#include <QTimer>
-#include <QTreeView>
#include "common/fs/fs_util.h"
-#include "common/fs/path_util.h"
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -42,15 +35,14 @@
#include "yuzu/uisettings.h"
#include "yuzu/util/util.h"
-ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id, const std::string& file_name,
+ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name,
Core::System& system_)
- : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()),
- title_id(title_id), system{system_} {
+ : QDialog(parent),
+ ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_} {
const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));
const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", title_id);
- game_config =
- std::make_unique<Config>(system, config_file_name, Config::ConfigType::PerGameConfig);
+ game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig);
addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this);
audio_tab = std::make_unique<ConfigureAudio>(system_, this);
@@ -123,8 +115,8 @@ void ConfigurePerGame::HandleApplyButtonClicked() {
ApplyConfiguration();
}
-void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file) {
- this->file = std::move(file);
+void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file_) {
+ file = std::move(file_);
LoadConfiguration();
}
diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h
index c1a57d87b..17a98a0f3 100644
--- a/src/yuzu/configuration/configure_per_game.h
+++ b/src/yuzu/configuration/configure_per_game.h
@@ -1,12 +1,10 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <string>
-#include <vector>
#include <QDialog>
#include <QList>
@@ -41,14 +39,14 @@ class ConfigurePerGame : public QDialog {
public:
// Cannot use std::filesystem::path due to https://bugreports.qt.io/browse/QTBUG-73263
- explicit ConfigurePerGame(QWidget* parent, u64 title_id, const std::string& file_name,
+ explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name,
Core::System& system_);
~ConfigurePerGame() override;
/// Save all button configurations to settings file
void ApplyConfiguration();
- void LoadFromFile(FileSys::VirtualFile file);
+ void LoadFromFile(FileSys::VirtualFile file_);
private:
void changeEvent(QEvent* event) override;
diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp
index 65e615963..674a75a62 100644
--- a/src/yuzu/configuration/configure_per_game_addons.cpp
+++ b/src/yuzu/configuration/configure_per_game_addons.cpp
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <memory>
@@ -24,7 +23,6 @@
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_per_game_addons.h"
#include "yuzu/uisettings.h"
-#include "yuzu/util/util.h"
ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* parent)
: QWidget(parent), ui{std::make_unique<Ui::ConfigurePerGameAddons>()}, system{system_} {
@@ -48,6 +46,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*>");
@@ -86,8 +88,8 @@ void ConfigurePerGameAddons::ApplyConfiguration() {
Settings::values.disabled_addons[title_id] = disabled_addons;
}
-void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file) {
- this->file = std::move(file);
+void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file_) {
+ file = std::move(file_);
LoadConfiguration();
}
@@ -139,5 +141,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_per_game_addons.h b/src/yuzu/configuration/configure_per_game_addons.h
index 24b017494..53db405c1 100644
--- a/src/yuzu/configuration/configure_per_game_addons.h
+++ b/src/yuzu/configuration/configure_per_game_addons.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -35,7 +34,7 @@ public:
/// Save all button configurations to settings file
void ApplyConfiguration();
- void LoadFromFile(FileSys::VirtualFile file);
+ void LoadFromFile(FileSys::VirtualFile file_);
void SetTitleId(u64 id);
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
index d9f6dee4e..5c0217ba8 100644
--- a/src/yuzu/configuration/configure_profile_manager.cpp
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -1,16 +1,13 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <QFileDialog>
#include <QGraphicsItem>
-#include <QGraphicsScene>
#include <QHeaderView>
#include <QMessageBox>
#include <QStandardItemModel>
#include <QTreeView>
-#include <QVBoxLayout>
#include "common/assert.h"
#include "common/fs/path_util.h"
#include "common/settings.h"
diff --git a/src/yuzu/configuration/configure_profile_manager.h b/src/yuzu/configuration/configure_profile_manager.h
index 575cb89d5..fe9033779 100644
--- a/src/yuzu/configuration/configure_profile_manager.h
+++ b/src/yuzu/configuration/configure_profile_manager.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp
new file mode 100644
index 000000000..688c2dd38
--- /dev/null
+++ b/src/yuzu/configuration/configure_ringcon.cpp
@@ -0,0 +1,423 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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_id2 = 0; sub_button_id2 < ANALOG_SUB_BUTTONS_NUM;
+ ++sub_button_id2) {
+ analog_map_buttons[sub_button_id2]->setText(
+ AnalogToText(param, analog_sub_buttons[sub_button_id2]));
+ }
+ });
+ 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..38a9cb716
--- /dev/null
+++ b/src/yuzu/configuration/configure_ringcon.h
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 56c762d64..bc9d9d77a 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -1,15 +1,12 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include <array>
#include <chrono>
#include <optional>
#include <QFileDialog>
#include <QGraphicsItem>
#include <QMessageBox>
-#include "common/assert.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/service/time/time_manager.h"
@@ -132,8 +129,7 @@ void ConfigureSystem::ApplyConfiguration() {
// Guard if during game and set to game-specific value
if (Settings::values.rng_seed.UsingGlobal()) {
if (ui->rng_seed_checkbox->isChecked()) {
- Settings::values.rng_seed.SetValue(
- ui->rng_seed_edit->text().toULongLong(nullptr, 16));
+ Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toUInt(nullptr, 16));
} else {
Settings::values.rng_seed.SetValue(std::nullopt);
}
@@ -144,8 +140,7 @@ void ConfigureSystem::ApplyConfiguration() {
case ConfigurationShared::CheckState::Off:
Settings::values.rng_seed.SetGlobal(false);
if (ui->rng_seed_checkbox->isChecked()) {
- Settings::values.rng_seed.SetValue(
- ui->rng_seed_edit->text().toULongLong(nullptr, 16));
+ Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toUInt(nullptr, 16));
} else {
Settings::values.rng_seed.SetValue(std::nullopt);
}
diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h
index bb24c9ae7..8f02880a7 100644
--- a/src/yuzu/configuration/configure_system.h
+++ b/src/yuzu/configuration/configure_system.h
@@ -1,12 +1,10 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
-#include <QList>
#include <QWidget>
namespace Core {
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index 5b68dcb29..b234ea87b 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -474,9 +474,6 @@
<day>1</day>
</date>
</property>
- <property name="displayFormat">
- <string>d MMM yyyy h:mm:ss AP</string>
- </property>
</widget>
</item>
<item row="6" column="1">
diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp
index 979a8db61..1edc5f1f3 100644
--- a/src/yuzu/configuration/configure_tas.cpp
+++ b/src/yuzu/configuration/configure_tas.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QFileDialog>
#include <QMessageBox>
diff --git a/src/yuzu/configuration/configure_tas.h b/src/yuzu/configuration/configure_tas.h
index 1546bf16f..4a6b0ba4e 100644
--- a/src/yuzu/configuration/configure_tas.h
+++ b/src/yuzu/configuration/configure_tas.h
@@ -1,11 +1,12 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QDialog>
+class QLineEdit;
+
namespace Ui {
class ConfigureTas;
}
diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui
index cf88a5bf0..625af0c89 100644
--- a/src/yuzu/configuration/configure_tas.ui
+++ b/src/yuzu/configuration/configure_tas.ui
@@ -16,6 +16,9 @@
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reads controller input from scripts in the same format as TAS-nx scripts.&lt;br/&gt;For a more detailed explanation, please consult the &lt;a href=&quot;https://yuzu-emu.org/help/feature/tas/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;help page&lt;/span&gt;&lt;/a&gt; on the yuzu website.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
</widget>
</item>
<item row="1" column="0" colspan="4">
diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp
index bde0a08c4..18e2eba69 100644
--- a/src/yuzu/configuration/configure_touch_from_button.cpp
+++ b/src/yuzu/configuration/configure_touch_from_button.cpp
@@ -1,12 +1,10 @@
-// Copyright 2020 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2020 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QInputDialog>
#include <QKeyEvent>
#include <QMessageBox>
#include <QMouseEvent>
-#include <QResizeEvent>
#include <QStandardItemModel>
#include <QTimer>
#include "common/param_package.h"
@@ -69,10 +67,10 @@ static QString ButtonToText(const Common::ParamPackage& param) {
}
ConfigureTouchFromButton::ConfigureTouchFromButton(
- QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps,
+ QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps_,
InputCommon::InputSubsystem* input_subsystem_, const int default_index)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchFromButton>()),
- touch_maps(touch_maps), input_subsystem{input_subsystem_}, selected_index(default_index),
+ touch_maps{touch_maps_}, input_subsystem{input_subsystem_}, selected_index{default_index},
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
ui->setupUi(this);
binding_list_model = new QStandardItemModel(0, 3, this);
@@ -227,6 +225,9 @@ void ConfigureTouchFromButton::RenameMapping() {
}
void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is_new) {
+ if (timeout_timer->isActive()) {
+ return;
+ }
binding_list_model->item(row_index, 0)->setText(tr("[press key]"));
input_setter = [this, row_index, is_new](const Common::ParamPackage& params,
diff --git a/src/yuzu/configuration/configure_touch_from_button.h b/src/yuzu/configuration/configure_touch_from_button.h
index e1400481a..5a1416d00 100644
--- a/src/yuzu/configuration/configure_touch_from_button.h
+++ b/src/yuzu/configuration/configure_touch_from_button.h
@@ -1,6 +1,5 @@
-// Copyright 2020 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2020 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -37,7 +36,7 @@ class ConfigureTouchFromButton : public QDialog {
public:
explicit ConfigureTouchFromButton(QWidget* parent,
- const std::vector<Settings::TouchFromButtonMap>& touch_maps,
+ const std::vector<Settings::TouchFromButtonMap>& touch_maps_,
InputCommon::InputSubsystem* input_subsystem_,
int default_index = 0);
~ConfigureTouchFromButton() override;
diff --git a/src/yuzu/configuration/configure_touch_widget.h b/src/yuzu/configuration/configure_touch_widget.h
index 347b46583..49f533afe 100644
--- a/src/yuzu/configuration/configure_touch_widget.h
+++ b/src/yuzu/configuration/configure_touch_widget.h
@@ -1,6 +1,5 @@
-// Copyright 2020 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2020 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
index 29c86c7bc..5a03e48df 100644
--- a/src/yuzu/configuration/configure_touchscreen_advanced.cpp
+++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include "ui_configure_touchscreen_advanced.h"
diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.h b/src/yuzu/configuration/configure_touchscreen_advanced.h
index 72061492c..034dc0d46 100644
--- a/src/yuzu/configuration/configure_touchscreen_advanced.h
+++ b/src/yuzu/configuration/configure_touchscreen_advanced.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 46e5409db..48f71b53c 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <utility>
@@ -9,6 +8,7 @@
#include <QDirIterator>
#include "common/common_types.h"
#include "common/fs/path_util.h"
+#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "ui_configure_ui.h"
@@ -170,14 +170,75 @@ void ConfigureUi::RetranslateUI() {
}
void ConfigureUi::InitializeLanguageComboBox() {
+ // This is a list of lexicographically sorted languages, only the available translations are
+ // shown to the user.
+ static const struct {
+ const QString name;
+ const char* id;
+ } languages[] = {
+ // clang-format off
+ {QStringLiteral(u"Bahasa Indonesia"), "id"}, // Indonesian
+ {QStringLiteral(u"Bahasa Melayu"), "ms"}, // Malay
+ {QStringLiteral(u"Catal\u00E0"), "ca"}, // Catalan
+ {QStringLiteral(u"\u010Ce\u0161tina"), "cs"}, // Czech
+ {QStringLiteral(u"Dansk"), "da"}, // Danish
+ {QStringLiteral(u"Deutsch"), "de"}, // German
+ {QStringLiteral(u"English"), "en"}, // English
+ {QStringLiteral(u"Espa\u00F1ol"), "es"}, // Spanish
+ {QStringLiteral(u"Fran\u00E7ais"), "fr"}, // French
+ {QStringLiteral(u"Hrvatski"), "hr"}, // Croatian
+ {QStringLiteral(u"Italiano"), "it"}, // Italian
+ {QStringLiteral(u"Magyar"), "hu"}, // Hungarian
+ {QStringLiteral(u"Nederlands"), "nl"}, // Dutch
+ {QStringLiteral(u"Norsk bokm\u00E5l"), "nb"}, // Norwegian
+ {QStringLiteral(u"Polski"), "pl"}, // Polish
+ {QStringLiteral(u"Portugu\u00EAs"), "pt_PT"}, // Portuguese
+ {QStringLiteral(u"Portugu\u00EAs (Brasil)"), "pt_BR"}, // Portuguese (Brazil)
+ {QStringLiteral(u"Rom\u00E2n\u0103"), "ro"}, // Romanian
+ {QStringLiteral(u"Srpski"), "sr"}, // Serbian
+ {QStringLiteral(u"Suomi"), "fi"}, // Finnish
+ {QStringLiteral(u"Svenska"), "sv"}, // Swedish
+ {QStringLiteral(u"Ti\u1EBFng Vi\u1EC7t"), "vi"}, // Vietnamese
+ {QStringLiteral(u"Ti\u1EBFng Vi\u1EC7t (Vi\u1EC7t Nam)"), "vi_VN"}, // Vietnamese
+ {QStringLiteral(u"T\u00FCrk\u00E7e"), "tr_TR"}, // Turkish
+ {QStringLiteral(u"\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC"), "el"}, // Greek
+ {QStringLiteral(u"\u0420\u0443\u0441\u0441\u043A\u0438\u0439"), "ru_RU"}, // Russian
+ {QStringLiteral(u"\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430"),
+ "uk"}, // Ukrainian
+ {QStringLiteral(u"\u0627\u0644\u0639\u0631\u0628\u064A\u0629"), "ar"}, // Arabic
+ {QStringLiteral(u"\u0641\u0627\u0631\u0633\u06CC"), "fa"}, // Farsi
+ {QStringLiteral(u"\uD55C\uAD6D\uC5B4"), "ko_KR"}, // Korean
+ {QStringLiteral(u"\u65E5\u672C\u8A9E"), "ja_JP"}, // Japanese
+ {QStringLiteral(u"\u7B80\u4F53\u4E2D\u6587"), "zh_CN"}, // Simplified Chinese
+ {QStringLiteral(u"\u7E41\u9AD4\u4E2D\u6587"), "zh_TW"}, // Traditional Chinese
+ // clang-format on
+ };
ui->language_combobox->addItem(tr("<System>"), QString{});
- ui->language_combobox->addItem(tr("English"), QStringLiteral("en"));
- QDirIterator it(QStringLiteral(":/languages"), QDirIterator::NoIteratorFlags);
- while (it.hasNext()) {
- QString locale = it.next();
+ QDir languages_dir{QStringLiteral(":/languages")};
+ QStringList language_files = languages_dir.entryList();
+ for (const auto& lang : languages) {
+ if (QString::fromLatin1(lang.id) == QStringLiteral("en")) {
+ ui->language_combobox->addItem(lang.name, QStringLiteral("en"));
+ language_files.removeOne(QStringLiteral("en.qm"));
+ continue;
+ }
+ for (int i = 0; i < language_files.size(); ++i) {
+ QString locale = language_files[i];
+ locale.truncate(locale.lastIndexOf(QLatin1Char{'.'}));
+ if (QString::fromLatin1(lang.id) == locale) {
+ ui->language_combobox->addItem(lang.name, locale);
+ language_files.removeAt(i);
+ break;
+ }
+ }
+ }
+ // Anything remaining will be at the bottom
+ for (const QString& file : language_files) {
+ LOG_CRITICAL(Frontend, "Unexpected Language File: {}", file.toStdString());
+ QString locale = file;
locale.truncate(locale.lastIndexOf(QLatin1Char{'.'}));
- locale.remove(0, locale.lastIndexOf(QLatin1Char{'/'}) + 1);
- const QString lang = QLocale::languageToString(QLocale(locale).language());
+ const QString language_name = QLocale::languageToString(QLocale(locale).language());
+ const QString lang = QStringLiteral("%1 [%2]").arg(language_name, locale);
ui->language_combobox->addItem(lang, locale);
}
diff --git a/src/yuzu/configuration/configure_ui.h b/src/yuzu/configuration/configure_ui.h
index 48b6e6d82..95af8370e 100644
--- a/src/yuzu/configuration/configure_ui.h
+++ b/src/yuzu/configuration/configure_ui.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp
index 779b6401c..d765e808a 100644
--- a/src/yuzu/configuration/configure_vibration.cpp
+++ b/src/yuzu/configuration/configure_vibration.cpp
@@ -1,13 +1,6 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
-#include <algorithm>
-#include <unordered_map>
-
-#include <fmt/format.h>
-
-#include "common/param_package.h"
#include "common/settings.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
diff --git a/src/yuzu/configuration/configure_vibration.h b/src/yuzu/configuration/configure_vibration.h
index 50b8195fa..e9d05df51 100644
--- a/src/yuzu/configuration/configure_vibration.h
+++ b/src/yuzu/configuration/configure_vibration.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp
index d779251b4..ab526e4ca 100644
--- a/src/yuzu/configuration/configure_web.cpp
+++ b/src/yuzu/configuration/configure_web.cpp
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QIcon>
#include <QMessageBox>
@@ -129,20 +128,25 @@ void ConfigureWeb::RefreshTelemetryID() {
void ConfigureWeb::OnLoginChanged() {
if (ui->edit_token->text().isEmpty()) {
user_verified = true;
-
- const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
- ui->label_token_verified->setPixmap(pixmap);
+ // Empty = no icon
+ ui->label_token_verified->setPixmap(QPixmap());
+ ui->label_token_verified->setToolTip(QString());
} else {
user_verified = false;
- const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
+ // Show an info icon if it's been changed, clearer than showing failure
+ const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("info")).pixmap(16);
ui->label_token_verified->setPixmap(pixmap);
+ ui->label_token_verified->setToolTip(
+ tr("Unverified, please click Verify before saving configuration", "Tooltip"));
}
}
void ConfigureWeb::VerifyLogin() {
ui->button_verify_login->setDisabled(true);
ui->button_verify_login->setText(tr("Verifying..."));
+ ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("sync")).pixmap(16));
+ ui->label_token_verified->setToolTip(tr("Verifying..."));
verify_watcher.setFuture(QtConcurrent::run(
[username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()),
token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] {
@@ -156,16 +160,21 @@ void ConfigureWeb::OnLoginVerified() {
if (verify_watcher.result()) {
user_verified = true;
- const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
- ui->label_token_verified->setPixmap(pixmap);
+ ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16));
+ ui->label_token_verified->setToolTip(tr("Verified", "Tooltip"));
ui->username->setText(
QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString())));
} else {
- const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
- ui->label_token_verified->setPixmap(pixmap);
+ ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16));
+ ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip"));
ui->username->setText(tr("Unspecified"));
QMessageBox::critical(this, tr("Verification failed"),
tr("Verification failed. Check that you have entered your token "
"correctly, and that your internet connection is working."));
}
}
+
+void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) {
+ ui->label_disable_info->setVisible(!enabled);
+ ui->groupBoxWebConfig->setEnabled(enabled);
+}
diff --git a/src/yuzu/configuration/configure_web.h b/src/yuzu/configuration/configure_web.h
index 9054711ea..03feb55f8 100644
--- a/src/yuzu/configuration/configure_web.h
+++ b/src/yuzu/configuration/configure_web.h
@@ -1,6 +1,5 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -20,6 +19,7 @@ public:
~ConfigureWeb() override;
void ApplyConfiguration();
+ void SetWebServiceConfigEnabled(bool enabled);
private:
void changeEvent(QEvent* event) override;
diff --git a/src/yuzu/configuration/configure_web.ui b/src/yuzu/configuration/configure_web.ui
index 35b4274b0..3ac3864be 100644
--- a/src/yuzu/configuration/configure_web.ui
+++ b/src/yuzu/configuration/configure_web.ui
@@ -113,6 +113,16 @@
</widget>
</item>
<item>
+ <widget class="QLabel" name="label_disable_info">
+ <property name="text">
+ <string>Web Service configuration can only be changed when a public room isn't being hosted.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Telemetry</string>
diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp
index 38ea6c772..9bb69cab1 100644
--- a/src/yuzu/configuration/input_profiles.cpp
+++ b/src/yuzu/configuration/input_profiles.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/format.h>
@@ -28,7 +27,7 @@ std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) {
} // namespace
-InputProfiles::InputProfiles(Core::System& system_) : system{system_} {
+InputProfiles::InputProfiles() {
const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input";
if (!FS::IsDir(input_profile_loc)) {
@@ -44,8 +43,8 @@ InputProfiles::InputProfiles(Core::System& system_) : system{system_} {
if (IsINI(filename) && IsProfileNameValid(name_without_ext)) {
map_profiles.insert_or_assign(
- name_without_ext, std::make_unique<Config>(system, name_without_ext,
- Config::ConfigType::InputProfile));
+ name_without_ext,
+ std::make_unique<Config>(name_without_ext, Config::ConfigType::InputProfile));
}
return true;
@@ -68,6 +67,8 @@ std::vector<std::string> InputProfiles::GetInputProfileNames() {
profile_names.push_back(profile_name);
}
+ std::stable_sort(profile_names.begin(), profile_names.end());
+
return profile_names;
}
@@ -81,8 +82,7 @@ bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t p
}
map_profiles.insert_or_assign(
- profile_name,
- std::make_unique<Config>(system, profile_name, Config::ConfigType::InputProfile));
+ profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile));
return SaveProfile(profile_name, player_index);
}
diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h
index a567bd5a9..2bf3e4250 100644
--- a/src/yuzu/configuration/input_profiles.h
+++ b/src/yuzu/configuration/input_profiles.h
@@ -1,11 +1,9 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
-#include <string_view>
#include <unordered_map>
namespace Core {
@@ -17,7 +15,7 @@ class Config;
class InputProfiles {
public:
- explicit InputProfiles(Core::System& system_);
+ explicit InputProfiles();
virtual ~InputProfiles();
std::vector<std::string> GetInputProfileNames();
@@ -33,6 +31,4 @@ private:
bool ProfileExistsInMap(const std::string& profile_name) const;
std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles;
-
- Core::System& system;
};
diff --git a/src/yuzu/debugger/console.cpp b/src/yuzu/debugger/console.cpp
index f89ea8ea7..1c1342ff1 100644
--- a/src/yuzu/debugger/console.cpp
+++ b/src/yuzu/debugger/console.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef _WIN32
#include <windows.h>
@@ -30,6 +29,7 @@ void ToggleConsole() {
freopen_s(&temp, "CONIN$", "r", stdin);
freopen_s(&temp, "CONOUT$", "w", stdout);
freopen_s(&temp, "CONOUT$", "w", stderr);
+ SetConsoleOutputCP(65001);
SetColorConsoleBackendEnabled(true);
}
} else {
diff --git a/src/yuzu/debugger/console.h b/src/yuzu/debugger/console.h
index d1990c496..fdb7d174c 100644
--- a/src/yuzu/debugger/console.h
+++ b/src/yuzu/debugger/console.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp
index 6b834c42e..e4bf16a04 100644
--- a/src/yuzu/debugger/controller.cpp
+++ b/src/yuzu/debugger/controller.cpp
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QAction>
#include <QLayout>
diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h
index 52cea3326..9651dfaa9 100644
--- a/src/yuzu/debugger/controller.h
+++ b/src/yuzu/debugger/controller.h
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp
index 33110685a..d3e2d3c12 100644
--- a/src/yuzu/debugger/profiler.cpp
+++ b/src/yuzu/debugger/profiler.cpp
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QAction>
#include <QLayout>
diff --git a/src/yuzu/debugger/profiler.h b/src/yuzu/debugger/profiler.h
index 8e69fdb06..4c8ccd3c2 100644
--- a/src/yuzu/debugger/profiler.h
+++ b/src/yuzu/debugger/profiler.h
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 2d1a2d9cb..7f7c5fc42 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -1,15 +1,12 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <fmt/format.h>
#include "yuzu/debugger/wait_tree.h"
#include "yuzu/uisettings.h"
-#include "yuzu/util/util.h"
-#include "common/assert.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/hle/kernel/k_class_token.h"
@@ -115,9 +112,9 @@ QString WaitTreeText::GetText() const {
return text;
}
-WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address, const Kernel::KHandleTable& handle_table,
+WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address_, const Kernel::KHandleTable& handle_table,
Core::System& system_)
- : mutex_address(mutex_address), system{system_} {
+ : mutex_address{mutex_address_}, system{system_} {
mutex_value = system.Memory().Read32(mutex_address);
owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Svc::HandleWaitMask);
owner = handle_table.GetObject<Kernel::KThread>(owner_handle).GetPointerUnsafe();
@@ -142,8 +139,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() cons
return list;
}
-WaitTreeCallstack::WaitTreeCallstack(const Kernel::KThread& thread, Core::System& system_)
- : thread(thread), system{system_} {}
+WaitTreeCallstack::WaitTreeCallstack(const Kernel::KThread& thread_, Core::System& system_)
+ : thread{thread_}, system{system_} {}
WaitTreeCallstack::~WaitTreeCallstack() = default;
QString WaitTreeCallstack::GetText() const {
@@ -173,8 +170,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons
}
WaitTreeSynchronizationObject::WaitTreeSynchronizationObject(
- const Kernel::KSynchronizationObject& o, Core::System& system_)
- : object(o), system{system_} {}
+ const Kernel::KSynchronizationObject& object_, Core::System& system_)
+ : object{object_}, system{system_} {}
WaitTreeSynchronizationObject::~WaitTreeSynchronizationObject() = default;
WaitTreeExpandableItem::WaitTreeExpandableItem() = default;
@@ -382,8 +379,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
return list;
}
-WaitTreeEvent::WaitTreeEvent(const Kernel::KReadableEvent& object, Core::System& system_)
- : WaitTreeSynchronizationObject(object, system_) {}
+WaitTreeEvent::WaitTreeEvent(const Kernel::KReadableEvent& object_, Core::System& system_)
+ : WaitTreeSynchronizationObject(object_, system_) {}
WaitTreeEvent::~WaitTreeEvent() = default;
WaitTreeThreadList::WaitTreeThreadList(std::vector<Kernel::KThread*>&& list, Core::System& system_)
diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h
index ea4d2e299..7e528b592 100644
--- a/src/yuzu/debugger/wait_tree.h
+++ b/src/yuzu/debugger/wait_tree.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,7 +7,6 @@
#include <memory>
#include <vector>
-#include <QAbstractItemModel>
#include <QDockWidget>
#include <QTreeView>
@@ -79,7 +77,7 @@ public:
class WaitTreeMutexInfo : public WaitTreeExpandableItem {
Q_OBJECT
public:
- explicit WaitTreeMutexInfo(VAddr mutex_address, const Kernel::KHandleTable& handle_table,
+ explicit WaitTreeMutexInfo(VAddr mutex_address_, const Kernel::KHandleTable& handle_table,
Core::System& system_);
~WaitTreeMutexInfo() override;
@@ -98,7 +96,7 @@ private:
class WaitTreeCallstack : public WaitTreeExpandableItem {
Q_OBJECT
public:
- explicit WaitTreeCallstack(const Kernel::KThread& thread, Core::System& system_);
+ explicit WaitTreeCallstack(const Kernel::KThread& thread_, Core::System& system_);
~WaitTreeCallstack() override;
QString GetText() const override;
@@ -113,7 +111,7 @@ private:
class WaitTreeSynchronizationObject : public WaitTreeExpandableItem {
Q_OBJECT
public:
- explicit WaitTreeSynchronizationObject(const Kernel::KSynchronizationObject& object,
+ explicit WaitTreeSynchronizationObject(const Kernel::KSynchronizationObject& object_,
Core::System& system_);
~WaitTreeSynchronizationObject() override;
@@ -163,7 +161,7 @@ private:
class WaitTreeEvent : public WaitTreeSynchronizationObject {
Q_OBJECT
public:
- explicit WaitTreeEvent(const Kernel::KReadableEvent& object, Core::System& system_);
+ explicit WaitTreeEvent(const Kernel::KReadableEvent& object_, Core::System& system_);
~WaitTreeEvent() override;
};
diff --git a/src/yuzu/discord.h b/src/yuzu/discord.h
index a867cc4d6..e08784498 100644
--- a/src/yuzu/discord.h
+++ b/src/yuzu/discord.h
@@ -1,6 +1,5 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/discord_impl.cpp b/src/yuzu/discord_impl.cpp
index 66f928af6..c351e9b83 100644
--- a/src/yuzu/discord_impl.cpp
+++ b/src/yuzu/discord_impl.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <string>
diff --git a/src/yuzu/discord_impl.h b/src/yuzu/discord_impl.h
index 03ad42681..84710b9c6 100644
--- a/src/yuzu/discord_impl.h
+++ b/src/yuzu/discord_impl.h
@@ -1,6 +1,5 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index e3661b390..b127badc2 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <regex>
#include <QApplication>
@@ -10,10 +9,10 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
-#include <QKeyEvent>
#include <QList>
#include <QMenu>
#include <QThreadPool>
+#include <QToolButton>
#include <fmt/format.h>
#include "common/common_types.h"
#include "common/logging/log.h"
@@ -28,8 +27,8 @@
#include "yuzu/uisettings.h"
#include "yuzu/util/controller_navigation.h"
-GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent)
- : QObject(parent), gamelist{gamelist} {}
+GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist_, QObject* parent)
+ : QObject(parent), gamelist{gamelist_} {}
// EventFilter in order to process systemkeys while editing the searchfield
bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) {
@@ -80,9 +79,9 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
return QObject::eventFilter(obj, event);
}
-void GameListSearchField::setFilterResult(int visible, int total) {
- this->visible = visible;
- this->total = total;
+void GameListSearchField::setFilterResult(int visible_, int total_) {
+ visible = visible_;
+ total = total_;
label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible));
}
@@ -127,10 +126,8 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
layout_filter = new QHBoxLayout;
layout_filter->setContentsMargins(8, 8, 8, 8);
label_filter = new QLabel;
- label_filter->setText(tr("Filter:"));
edit_filter = new QLineEdit;
edit_filter->clear();
- edit_filter->setPlaceholderText(tr("Enter pattern to filter"));
edit_filter->installEventFilter(key_release_eater);
edit_filter->setClearButtonEnabled(true);
connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::OnTextChanged);
@@ -150,6 +147,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
layout_filter->addWidget(label_filter_result);
layout_filter->addWidget(button_filter_close);
setLayout(layout_filter);
+ RetranslateUI();
}
/**
@@ -287,7 +285,7 @@ void GameList::OnUpdateThemedIcons() {
}
case GameListItemType::AddDir:
child->setData(
- QIcon::fromTheme(QStringLiteral("plus"))
+ QIcon::fromTheme(QStringLiteral("list-add"))
.pixmap(icon_size)
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
Qt::DecorationRole);
@@ -309,9 +307,9 @@ void GameList::OnFilterCloseClicked() {
main_window->filterBarSetChecked(false);
}
-GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvider* provider,
+GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_,
Core::System& system_, GMainWindow* parent)
- : QWidget{parent}, vfs(std::move(vfs)), provider(provider), system{system_} {
+ : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} {
watcher = new QFileSystemWatcher(this);
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
@@ -334,13 +332,9 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }"));
item_model->insertColumns(0, COLUMN_COUNT);
- item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));
- item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility"));
+ RetranslateUI();
- item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
- item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
- item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
item_model->setSortRole(GameListItemPath::SortRole);
connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
@@ -483,7 +477,7 @@ void GameList::DonePopulating(const QStringList& watch_list) {
// Also artificially caps the watcher to a certain number of directories
constexpr int LIMIT_WATCH_DIRECTORIES = 5000;
constexpr int SLICE_SIZE = 25;
- int len = std::min(watch_list.length(), LIMIT_WATCH_DIRECTORIES);
+ int len = std::min(static_cast<int>(watch_list.size()), LIMIT_WATCH_DIRECTORIES);
for (int i = 0; i < len; i += SLICE_SIZE) {
watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE));
QCoreApplication::processEvents();
@@ -499,6 +493,8 @@ void GameList::DonePopulating(const QStringList& watch_list) {
}
item_model->sort(tree_view->header()->sortIndicatorSection(),
tree_view->header()->sortIndicatorOrder());
+
+ emit PopulatingCompleted();
}
void GameList::PopupContextMenu(const QPoint& menu_location) {
@@ -752,6 +748,39 @@ void GameList::LoadCompatibilityList() {
}
}
+void GameList::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void GameList::RetranslateUI() {
+ item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));
+ item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility"));
+ item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
+ item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
+ item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
+}
+
+void GameListSearchField::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void GameListSearchField::RetranslateUI() {
+ label_filter->setText(tr("Filter:"));
+ edit_filter->setPlaceholderText(tr("Enter pattern to filter"));
+}
+
+QStandardItemModel* GameList::GetModel() const {
+ return item_model;
+}
+
void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
tree_view->setEnabled(false);
@@ -870,7 +899,7 @@ GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent}
layout->setAlignment(Qt::AlignCenter);
image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200));
- text->setText(tr("Double-click to add a new folder to the game list"));
+ RetranslateUI();
QFont font = text->font();
font.setPointSize(20);
text->setFont(font);
@@ -891,3 +920,15 @@ void GameListPlaceholder::onUpdateThemedIcons() {
void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) {
emit GameListPlaceholder::AddDirectory();
}
+
+void GameListPlaceholder::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void GameListPlaceholder::RetranslateUI() {
+ text->setText(tr("Double-click to add a new folder to the game list"));
+}
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index a94ea1477..cdf085019 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -1,29 +1,28 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QFileSystemWatcher>
-#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QList>
-#include <QModelIndex>
-#include <QSettings>
-#include <QStandardItem>
#include <QStandardItemModel>
#include <QString>
-#include <QToolButton>
#include <QTreeView>
#include <QVBoxLayout>
#include <QVector>
#include <QWidget>
#include "common/common_types.h"
+#include "core/core.h"
#include "uisettings.h"
#include "yuzu/compatibility_list.h"
+namespace Core {
+class System;
+}
+
class ControllerNavigation;
class GameListWorker;
class GameListSearchField;
@@ -72,8 +71,8 @@ public:
COLUMN_COUNT, // Number of columns
};
- explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs,
- FileSys::ManualContentProvider* provider, Core::System& system_,
+ explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
+ FileSys::ManualContentProvider* provider_, Core::System& system_,
GMainWindow* parent = nullptr);
~GameList() override;
@@ -89,6 +88,8 @@ public:
void SaveInterfaceLayout();
void LoadInterfaceLayout();
+ QStandardItemModel* GetModel() const;
+
/// Disables events from the emulated controller
void UnloadController();
@@ -113,6 +114,7 @@ signals:
void OpenDirectory(const QString& directory);
void AddDirectory();
void ShowList(bool show);
+ void PopulatingCompleted();
private slots:
void OnItemExpanded(const QModelIndex& item);
@@ -138,6 +140,9 @@ private:
void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
void AddFavoritesPopup(QMenu& context_menu);
+ void changeEvent(QEvent*) override;
+ void RetranslateUI();
+
std::shared_ptr<FileSys::VfsFilesystem> vfs;
FileSys::ManualContentProvider* provider;
GameListSearchField* search_field;
@@ -171,6 +176,9 @@ protected:
void mouseDoubleClickEvent(QMouseEvent* event) override;
private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
QVBoxLayout* layout = nullptr;
QLabel* image = nullptr;
QLabel* text = nullptr;
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 9dc3cc7c3..6198d1e4e 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -11,7 +10,6 @@
#include <QCoreApplication>
#include <QFileInfo>
-#include <QImage>
#include <QObject>
#include <QStandardItem>
#include <QString>
@@ -165,8 +163,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);
}
@@ -226,8 +224,8 @@ public:
static constexpr int GameDirRole = Qt::UserRole + 2;
explicit GameListDir(UISettings::GameDir& directory,
- GameListItemType dir_type = GameListItemType::CustomDir)
- : dir_type{dir_type} {
+ GameListItemType dir_type_ = GameListItemType::CustomDir)
+ : dir_type{dir_type_} {
setData(type(), TypeRole);
UISettings::GameDir* game_dir = &directory;
@@ -296,7 +294,7 @@ public:
const int icon_size = UISettings::values.folder_icon_size.GetValue();
- setData(QIcon::fromTheme(QStringLiteral("plus"))
+ setData(QIcon::fromTheme(QStringLiteral("list-add"))
.pixmap(icon_size)
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
Qt::DecorationRole);
@@ -349,15 +347,18 @@ public:
explicit GameListSearchField(GameList* parent = nullptr);
QString filterText() const;
- void setFilterResult(int visible, int total);
+ void setFilterResult(int visible_, int total_);
void clear();
void setFocus();
private:
+ void changeEvent(QEvent*) override;
+ void RetranslateUI();
+
class KeyReleaseEater : public QObject {
public:
- explicit KeyReleaseEater(GameList* gamelist, QObject* parent = nullptr);
+ explicit KeyReleaseEater(GameList* gamelist_, QObject* parent = nullptr);
private:
GameList* gamelist = nullptr;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index fd92b36df..63326968b 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include <string>
@@ -23,7 +22,6 @@
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h"
-#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/game_list.h"
@@ -225,12 +223,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
}
} // Anonymous namespace
-GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs,
- FileSys::ManualContentProvider* provider,
- QVector<UISettings::GameDir>& game_dirs,
- const CompatibilityList& compatibility_list, Core::System& system_)
- : vfs(std::move(vfs)), provider(provider), game_dirs(game_dirs),
- compatibility_list(compatibility_list), system{system_} {}
+GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
+ FileSys::ManualContentProvider* provider_,
+ QVector<UISettings::GameDir>& game_dirs_,
+ const CompatibilityList& compatibility_list_, Core::System& system_)
+ : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
+ compatibility_list{compatibility_list_}, system{system_} {}
GameListWorker::~GameListWorker() = default;
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 1383e9fbc..24a4e92c3 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -1,22 +1,17 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
-#include <map>
#include <memory>
#include <string>
-#include <unordered_map>
#include <QList>
#include <QObject>
#include <QRunnable>
#include <QString>
-#include <QVector>
-#include "common/common_types.h"
#include "yuzu/compatibility_list.h"
namespace Core {
@@ -38,10 +33,10 @@ class GameListWorker : public QObject, public QRunnable {
Q_OBJECT
public:
- explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs,
- FileSys::ManualContentProvider* provider,
- QVector<UISettings::GameDir>& game_dirs,
- const CompatibilityList& compatibility_list, Core::System& system_);
+ explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
+ FileSys::ManualContentProvider* provider_,
+ QVector<UISettings::GameDir>& game_dirs_,
+ const CompatibilityList& compatibility_list_, Core::System& system_);
~GameListWorker() override;
/// Starts the processing of directory tree information.
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp
index 6ed9611c7..13723f6e5 100644
--- a/src/yuzu/hotkeys.cpp
+++ b/src/yuzu/hotkeys.cpp
@@ -1,9 +1,7 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <sstream>
-#include <QKeySequence>
#include <QShortcut>
#include <QTreeWidgetItem>
#include <QtGlobal>
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index 57a7c7da5..dc5b7f628 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/install_dialog.cpp b/src/yuzu/install_dialog.cpp
index 06b0b1874..84ec4fe13 100644
--- a/src/yuzu/install_dialog.cpp
+++ b/src/yuzu/install_dialog.cpp
@@ -1,11 +1,9 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QFileInfo>
-#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QVBoxLayout>
diff --git a/src/yuzu/install_dialog.h b/src/yuzu/install_dialog.h
index 68e03fe4e..4c7c3c158 100644
--- a/src/yuzu/install_dialog.h
+++ b/src/yuzu/install_dialog.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp
index b001b8c23..e263a07a7 100644
--- a/src/yuzu/loading_screen.cpp
+++ b/src/yuzu/loading_screen.cpp
@@ -1,24 +1,16 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <unordered_map>
#include <QBuffer>
#include <QByteArray>
#include <QGraphicsOpacityEffect>
-#include <QHBoxLayout>
#include <QIODevice>
#include <QImage>
-#include <QLabel>
#include <QPainter>
-#include <QPalette>
#include <QPixmap>
-#include <QProgressBar>
#include <QPropertyAnimation>
#include <QStyleOption>
-#include <QTime>
-#include <QtConcurrent/QtConcurrentRun>
-#include "common/logging/log.h"
#include "core/frontend/framebuffer_layout.h"
#include "core/loader/loader.h"
#include "ui_loading_screen.h"
@@ -155,6 +147,10 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size
ui->progress_bar->setMaximum(static_cast<int>(total));
previous_total = total;
}
+ // Reset the progress bar ranges if compilation is done
+ if (stage == VideoCore::LoadCallbackStage::Complete) {
+ ui->progress_bar->setRange(0, 0);
+ }
QString estimate;
// If theres a drastic slowdown in the rate, then display an estimate
@@ -191,7 +187,7 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size
void LoadingScreen::paintEvent(QPaintEvent* event) {
QStyleOption opt;
- opt.init(this);
+ opt.initFrom(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
QWidget::paintEvent(event);
diff --git a/src/yuzu/loading_screen.h b/src/yuzu/loading_screen.h
index 29155a77c..17045595d 100644
--- a/src/yuzu/loading_screen.h
+++ b/src/yuzu/loading_screen.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -8,6 +7,7 @@
#include <memory>
#include <QString>
#include <QWidget>
+#include <QtGlobal>
#if !QT_CONFIG(movie)
#define YUZU_QT_MOVIE_MISSING 1
@@ -89,4 +89,6 @@ private:
std::size_t slow_shader_first_value = 0;
};
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
Q_DECLARE_METATYPE(VideoCore::LoadCallbackStage);
+#endif
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 3c2d7d080..4146ebc2c 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1,14 +1,18 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cinttypes>
#include <clocale>
+#include <cmath>
#include <memory>
#include <thread>
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
+#ifdef __linux__
+#include <csignal>
+#include <sys/socket.h>
+#endif
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/qt_controller.h"
@@ -20,11 +24,11 @@
#include "configuration/configure_input.h"
#include "configuration/configure_per_game.h"
#include "configuration/configure_tas.h"
-#include "configuration/configure_vibration.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/general_frontend.h"
+#include "core/frontend/applets/mii_edit.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
@@ -32,6 +36,7 @@
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h"
+#include "yuzu/multiplayer/state.h"
#include "yuzu/util/controller_navigation.h"
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
@@ -52,9 +57,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#define QT_NO_OPENGL
#include <QClipboard>
#include <QDesktopServices>
-#include <QDesktopWidget>
-#include <QDialogButtonBox>
-#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QInputDialog>
@@ -62,6 +64,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QProgressBar>
#include <QProgressDialog>
#include <QPushButton>
+#include <QScreen>
#include <QShortcut>
#include <QStatusBar>
#include <QString>
@@ -76,11 +79,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <fmt/format.h>
#include "common/detached_tasks.h"
#include "common/fs/fs.h"
-#include "common/fs/fs_paths.h"
#include "common/fs/path_util.h"
#include "common/literals.h"
#include "common/logging/backend.h"
-#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "common/memory_detect.h"
#include "common/microprofile.h"
@@ -105,12 +106,12 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/filesystem/filesystem.h"
-#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h"
#include "core/telemetry_session.h"
#include "input_common/drivers/tas_input.h"
+#include "input_common/drivers/virtual_amiibo.h"
#include "input_common/main.h"
#include "ui_main.h"
#include "util/overlay_dialog.h"
@@ -134,7 +135,13 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/install_dialog.h"
#include "yuzu/loading_screen.h"
#include "yuzu/main.h"
+#include "yuzu/startup_checks.h"
#include "yuzu/uisettings.h"
+#include "yuzu/util/clickable_label.h"
+
+#ifdef YUZU_DBGHELP
+#include "yuzu/mini_dump.h"
+#endif
using namespace Common::Literals;
@@ -156,7 +163,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
@@ -201,12 +209,80 @@ static void RemoveCachedContents() {
Common::FS::RemoveDirRecursively(offline_system_data);
}
-GMainWindow::GMainWindow()
+static void LogRuntimes() {
+#ifdef _MSC_VER
+ // It is possible that the name of the dll will change.
+ // vcruntime140.dll is for 2015 and onwards
+ constexpr char runtime_dll_name[] = "vcruntime140.dll";
+ UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr);
+ bool runtime_version_inspection_worked = false;
+ if (sz > 0) {
+ std::vector<u8> buf(sz);
+ if (GetFileVersionInfoA(runtime_dll_name, 0, sz, buf.data())) {
+ VS_FIXEDFILEINFO* pvi;
+ sz = sizeof(VS_FIXEDFILEINFO);
+ if (VerQueryValueA(buf.data(), "\\", reinterpret_cast<LPVOID*>(&pvi), &sz)) {
+ if (pvi->dwSignature == VS_FFI_SIGNATURE) {
+ runtime_version_inspection_worked = true;
+ LOG_INFO(Frontend, "MSVC Compiler: {} Runtime: {}.{}.{}.{}", _MSC_VER,
+ pvi->dwProductVersionMS >> 16, pvi->dwProductVersionMS & 0xFFFF,
+ pvi->dwProductVersionLS >> 16, pvi->dwProductVersionLS & 0xFFFF);
+ }
+ }
+ }
+ }
+ if (!runtime_version_inspection_worked) {
+ LOG_INFO(Frontend, "Unable to inspect {}", runtime_dll_name);
+ }
+#endif
+}
+
+static QString PrettyProductName() {
+#ifdef _WIN32
+ // After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2
+ // With that notation change they changed the registry key used to denote the current version
+ QSettings windows_registry(
+ QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
+ QSettings::NativeFormat);
+ const QString release_id = windows_registry.value(QStringLiteral("ReleaseId")).toString();
+ if (release_id == QStringLiteral("2009")) {
+ const u32 current_build = windows_registry.value(QStringLiteral("CurrentBuild")).toUInt();
+ const QString display_version =
+ windows_registry.value(QStringLiteral("DisplayVersion")).toString();
+ const u32 ubr = windows_registry.value(QStringLiteral("UBR")).toUInt();
+ u32 version = 10;
+ if (current_build >= 22000) {
+ version = 11;
+ }
+ return QStringLiteral("Windows %1 Version %2 (Build %3.%4)")
+ .arg(QString::number(version), display_version, QString::number(current_build),
+ QString::number(ubr));
+ }
+#endif
+ return QSysInfo::prettyProductName();
+}
+
+bool GMainWindow::CheckDarkMode() {
+#ifdef __linux__
+ const QPalette test_palette(qApp->palette());
+ const QColor text_color = test_palette.color(QPalette::Active, QPalette::Text);
+ const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window);
+ return (text_color.value() > window_color.value());
+#else
+ // TODO: Windows
+ return false;
+#endif // __linux__
+}
+
+GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan)
: ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()},
- input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
- config{std::make_unique<Config>(*system)},
+ input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)},
vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
provider{std::make_unique<FileSys::ManualContentProvider>()} {
+#ifdef __linux__
+ SetupSigInterrupts();
+#endif
+
Common::Log::Initialize();
LoadTranslation();
@@ -214,12 +290,21 @@ GMainWindow::GMainWindow()
ui->setupUi(this);
statusBar()->hide();
+ // Check dark mode before a theme is loaded
+ os_dark_mode = CheckDarkMode();
+ startup_icon_theme = QIcon::themeName();
+ // fallback can only be set once, colorful theme icons are okay on both light/dark
+ QIcon::setFallbackThemeName(QStringLiteral("colorful"));
+ QIcon::setFallbackSearchPaths(QStringList(QStringLiteral(":/icons")));
+
default_theme_paths = QIcon::themeSearchPaths();
UpdateUITheme();
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
discord_rpc->Update();
+ system->GetRoomNetwork().Init();
+
RegisterMetaTypes();
InitializeWidgets();
@@ -246,12 +331,13 @@ GMainWindow::GMainWindow()
const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;
LOG_INFO(Frontend, "yuzu Version: {}", yuzu_build_version);
+ LogRuntimes();
#ifdef ARCHITECTURE_x86_64
const auto& caps = Common::GetCPUCaps();
std::string cpu_string = caps.cpu_string;
- if (caps.avx || caps.avx2 || caps.avx512) {
+ if (caps.avx || caps.avx2 || caps.avx512f) {
cpu_string += " | AVX";
- if (caps.avx512) {
+ if (caps.avx512f) {
cpu_string += "512";
} else if (caps.avx2) {
cpu_string += '2';
@@ -262,7 +348,7 @@ GMainWindow::GMainWindow()
}
LOG_INFO(Frontend, "Host CPU: {}", cpu_string);
#endif
- LOG_INFO(Frontend, "Host OS: {}", QSysInfo::prettyProductName().toStdString());
+ LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString());
LOG_INFO(Frontend, "Host RAM: {:.2f} GiB",
Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB});
LOG_INFO(Frontend, "Host Swap: {:.2f} GiB", Common::GetMemInfo().TotalSwapMemory / f64{1_GiB});
@@ -291,13 +377,29 @@ 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);
+ mouse_center_timer.setInterval(default_mouse_center_timeout);
+ connect(&mouse_center_timer, &QTimer::timeout, this, &GMainWindow::CenterMouseCursor);
+
MigrateConfigFiles();
- ui->action_Fullscreen->setChecked(false);
+ if (has_broken_vulkan) {
+ UISettings::values.has_broken_vulkan = true;
+
+ QMessageBox::warning(this, tr("Broken Vulkan Installation Detected"),
+ tr("Vulkan initialization failed during boot.<br><br>Click <a "
+ "href='https://yuzu-emu.org/wiki/faq/"
+ "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>"
+ "here for instructions to fix the issue</a>."));
+
+ Settings::values.renderer_backend = Settings::RendererBackend::OpenGL;
+
+ renderer_status_button->setDisabled(true);
+ renderer_status_button->setChecked(false);
+ }
#if defined(HAVE_SDL2) && !defined(_WIN32)
SDL_InitSubSystem(SDL_INIT_VIDEO);
@@ -307,6 +409,8 @@ GMainWindow::GMainWindow()
SDL_EnableScreenSaver();
#endif
+ SetupPrepareForSleep();
+
Common::Log::Start();
QStringList args = QApplication::arguments();
@@ -316,17 +420,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;
}
@@ -369,9 +476,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);
}
@@ -382,6 +495,11 @@ GMainWindow::~GMainWindow() {
if (render_window->parent() == nullptr) {
delete render_window;
}
+
+#ifdef __linux__
+ ::close(sig_interrupt_fds[0]);
+ ::close(sig_interrupt_fds[1]);
+#endif
}
void GMainWindow::RegisterMetaTypes() {
@@ -586,7 +704,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;
@@ -651,12 +769,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);
}
});
@@ -745,6 +863,10 @@ void GMainWindow::InitializeWidgets() {
}
});
+ multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui->action_Leave_Room,
+ ui->action_Show_Room, *system);
+ multiplayer_state->setVisible(false);
+
// Create status bar
message_label = new QLabel();
// Configured separately for left alignment
@@ -777,6 +899,10 @@ void GMainWindow::InitializeWidgets() {
statusBar()->addPermanentWidget(label);
}
+ // TODO (flTobi): Add the widget when multiplayer is fully implemented
+ statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
+ statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
+
tas_label = new QLabel();
tas_label->setObjectName(QStringLiteral("TASlabel"));
tas_label->setFocusPolicy(Qt::NoFocus);
@@ -820,12 +946,11 @@ void GMainWindow::InitializeWidgets() {
// Setup Dock button
dock_status_button = new QPushButton();
- dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
+ dock_status_button->setObjectName(QStringLiteral("DockingStatusBarButton"));
dock_status_button->setFocusPolicy(Qt::NoFocus);
connect(dock_status_button, &QPushButton::clicked, this, &GMainWindow::OnToggleDockedMode);
- dock_status_button->setText(tr("DOCK"));
dock_status_button->setCheckable(true);
- dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
+ UpdateDockedButton();
statusBar()->insertPermanentWidget(0, dock_status_button);
gpu_accuracy_button = new QPushButton();
@@ -856,8 +981,7 @@ void GMainWindow::InitializeWidgets() {
Settings::values.renderer_backend.SetValue(Settings::RendererBackend::Vulkan);
} else {
Settings::values.renderer_backend.SetValue(Settings::RendererBackend::OpenGL);
- const auto filter = Settings::values.scaling_filter.GetValue();
- if (filter == Settings::ScalingFilter::Fsr) {
+ if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) {
Settings::values.scaling_filter.SetValue(Settings::ScalingFilter::NearestNeighbor);
UpdateFilterText();
}
@@ -926,15 +1050,16 @@ void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name
auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
const auto* controller_hotkey =
hotkey_registry.GetControllerHotkey(main_window, action_name, controller);
- connect(controller_hotkey, &ControllerShortcut::Activated, this,
- [action] { action->trigger(); });
+ connect(
+ controller_hotkey, &ControllerShortcut::Activated, this, [action] { action->trigger(); },
+ Qt::QueuedConnection);
}
void GMainWindow::InitializeHotkeys() {
hotkey_registry.LoadHotkeys();
LinkActionShortcut(ui->action_Load_File, QStringLiteral("Load File"));
- LinkActionShortcut(ui->action_Load_Amiibo, QStringLiteral("Load Amiibo"));
+ LinkActionShortcut(ui->action_Load_Amiibo, QStringLiteral("Load/Remove Amiibo"));
LinkActionShortcut(ui->action_Exit, QStringLiteral("Exit yuzu"));
LinkActionShortcut(ui->action_Restart, QStringLiteral("Restart Emulation"));
LinkActionShortcut(ui->action_Pause, QStringLiteral("Continue/Pause Emulation"));
@@ -954,7 +1079,8 @@ void GMainWindow::InitializeHotkeys() {
const auto* controller_hotkey =
hotkey_registry.GetControllerHotkey(main_window, action_name, controller);
connect(hotkey, &QShortcut::activated, this, function);
- connect(controller_hotkey, &ControllerShortcut::Activated, this, function);
+ connect(controller_hotkey, &ControllerShortcut::Activated, this, function,
+ Qt::QueuedConnection);
};
connect_shortcut(QStringLiteral("Exit Fullscreen"), [&] {
@@ -970,17 +1096,29 @@ void GMainWindow::InitializeHotkeys() {
connect_shortcut(QStringLiteral("Audio Mute/Unmute"),
[] { Settings::values.audio_muted = !Settings::values.audio_muted; });
connect_shortcut(QStringLiteral("Audio Volume Down"), [] {
- const auto current_volume = static_cast<int>(Settings::values.volume.GetValue());
- const auto new_volume = std::max(current_volume - 5, 0);
- Settings::values.volume.SetValue(static_cast<u8>(new_volume));
+ const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue());
+ int step = 5;
+ if (current_volume <= 30) {
+ step = 2;
+ }
+ if (current_volume <= 6) {
+ step = 1;
+ }
+ Settings::values.volume.SetValue(std::max(current_volume - step, 0));
});
connect_shortcut(QStringLiteral("Audio Volume Up"), [] {
- const auto current_volume = static_cast<int>(Settings::values.volume.GetValue());
- const auto new_volume = std::min(current_volume + 5, 100);
- Settings::values.volume.SetValue(static_cast<u8>(new_volume));
+ const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue());
+ int step = 5;
+ if (current_volume < 30) {
+ step = 2;
+ }
+ if (current_volume < 6) {
+ step = 1;
+ }
+ Settings::values.volume.SetValue(current_volume + step);
});
connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] {
- Settings::values.disable_fps_limit.SetValue(!Settings::values.disable_fps_limit.GetValue());
+ Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue());
});
connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
Settings::values.mouse_panning = !Settings::values.mouse_panning;
@@ -993,7 +1131,7 @@ void GMainWindow::InitializeHotkeys() {
void GMainWindow::SetDefaultUIGeometry() {
// geometry: 53% of the window contents are in the upper screen half, 47% in the lower half
- const QRect screenRect = QApplication::desktop()->screenGeometry(this);
+ const QRect screenRect = QGuiApplication::primaryScreen()->geometry();
const int w = screenRect.width() * 2 / 3;
const int h = screenRect.height() * 2 / 3;
@@ -1006,6 +1144,10 @@ void GMainWindow::SetDefaultUIGeometry() {
void GMainWindow::RestoreUIState() {
setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint);
restoreGeometry(UISettings::values.geometry);
+ // Work-around because the games list isn't supposed to be full screen
+ if (isFullScreen()) {
+ showNormal();
+ }
restoreState(UISettings::values.state);
render_window->setWindowFlags(render_window->windowFlags() & ~Qt::FramelessWindowHint);
render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
@@ -1048,6 +1190,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
OnPauseGame();
} else if (!emu_thread->IsRunning() && auto_paused && state == Qt::ApplicationActive) {
auto_paused = false;
+ RequestGameResume();
OnStartGame();
}
}
@@ -1081,6 +1224,8 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
&GMainWindow::OnGameListAddDirectory);
connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList);
+ connect(game_list, &GameList::PopulatingCompleted,
+ [this] { multiplayer_state->UpdateGameList(game_list->GetModel()); });
connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
&GMainWindow::OnGameListOpenPerGameProperties);
@@ -1098,6 +1243,9 @@ void GMainWindow::ConnectWidgetEvents() {
connect(this, &GMainWindow::EmulationStopping, this, &GMainWindow::SoftwareKeyboardExit);
connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar);
+
+ connect(this, &GMainWindow::UpdateThemedIcons, multiplayer_state,
+ &MultiplayerState::UpdateThemedIcons);
}
void GMainWindow::ConnectMenuEvents() {
@@ -1141,6 +1289,19 @@ void GMainWindow::ConnectMenuEvents() {
ui->action_Reset_Window_Size_900,
ui->action_Reset_Window_Size_1080});
+ // Multiplayer
+ connect(ui->action_View_Lobby, &QAction::triggered, multiplayer_state,
+ &MultiplayerState::OnViewLobby);
+ connect(ui->action_Start_Room, &QAction::triggered, multiplayer_state,
+ &MultiplayerState::OnCreateRoom);
+ connect(ui->action_Leave_Room, &QAction::triggered, multiplayer_state,
+ &MultiplayerState::OnCloseRoom);
+ connect(ui->action_Connect_To_Room, &QAction::triggered, multiplayer_state,
+ &MultiplayerState::OnDirectConnectToRoom);
+ connect(ui->action_Show_Room, &QAction::triggered, multiplayer_state,
+ &MultiplayerState::OnOpenNetworkRoom);
+ connect(multiplayer_state, &MultiplayerState::SaveConfig, this, &GMainWindow::OnSaveConfig);
+
// Tools
connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
ReinitializeKeyBehavior::Warning));
@@ -1180,6 +1341,8 @@ void GMainWindow::UpdateMenuState() {
} else {
ui->action_Pause->setText(tr("&Pause"));
}
+
+ multiplayer_state->UpdateNotificationStatus();
}
void GMainWindow::OnDisplayTitleBars(bool show) {
@@ -1202,6 +1365,43 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
}
}
+void GMainWindow::SetupPrepareForSleep() {
+#ifdef __linux__
+ auto bus = QDBusConnection::systemBus();
+ if (bus.isConnected()) {
+ const bool success = bus.connect(
+ QStringLiteral("org.freedesktop.login1"), QStringLiteral("/org/freedesktop/login1"),
+ QStringLiteral("org.freedesktop.login1.Manager"), QStringLiteral("PrepareForSleep"),
+ QStringLiteral("b"), this, SLOT(OnPrepareForSleep(bool)));
+
+ if (!success) {
+ LOG_WARNING(Frontend, "Couldn't register PrepareForSleep signal");
+ }
+ } else {
+ LOG_WARNING(Frontend, "QDBusConnection system bus is not connected");
+ }
+#endif // __linux__
+}
+
+void GMainWindow::OnPrepareForSleep(bool prepare_sleep) {
+ if (emu_thread == nullptr) {
+ return;
+ }
+
+ if (prepare_sleep) {
+ if (emu_thread->IsRunning()) {
+ auto_paused = true;
+ OnPauseGame();
+ }
+ } else {
+ if (!emu_thread->IsRunning() && auto_paused) {
+ auto_paused = false;
+ RequestGameResume();
+ OnStartGame();
+ }
+ }
+}
+
#ifdef __linux__
static std::optional<QDBusObjectPath> HoldWakeLockLinux(u32 window_id = 0) {
if (!QDBusConnection::sessionBus().isConnected()) {
@@ -1241,6 +1441,52 @@ static void ReleaseWakeLockLinux(QDBusObjectPath lock) {
QString::fromLatin1("org.freedesktop.portal.Request"));
unlocker.call(QString::fromLatin1("Close"));
}
+
+std::array<int, 3> GMainWindow::sig_interrupt_fds{0, 0, 0};
+
+void GMainWindow::SetupSigInterrupts() {
+ if (sig_interrupt_fds[2] == 1) {
+ return;
+ }
+ socketpair(AF_UNIX, SOCK_STREAM, 0, sig_interrupt_fds.data());
+ sig_interrupt_fds[2] = 1;
+
+ struct sigaction sa;
+ sa.sa_handler = &GMainWindow::HandleSigInterrupt;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESETHAND;
+ sigaction(SIGINT, &sa, nullptr);
+ sigaction(SIGTERM, &sa, nullptr);
+
+ sig_interrupt_notifier = new QSocketNotifier(sig_interrupt_fds[1], QSocketNotifier::Read, this);
+ connect(sig_interrupt_notifier, &QSocketNotifier::activated, this,
+ &GMainWindow::OnSigInterruptNotifierActivated);
+ connect(this, &GMainWindow::SigInterrupt, this, &GMainWindow::close);
+}
+
+void GMainWindow::HandleSigInterrupt(int sig) {
+ if (sig == SIGINT) {
+ exit(1);
+ }
+
+ // Calling into Qt directly from a signal handler is not safe,
+ // so wake up a QSocketNotifier with this hacky write call instead.
+ char a = 1;
+ int ret = write(sig_interrupt_fds[0], &a, sizeof(a));
+ (void)ret;
+}
+
+void GMainWindow::OnSigInterruptNotifierActivated() {
+ sig_interrupt_notifier->setEnabled(false);
+
+ char a;
+ int ret = read(sig_interrupt_fds[1], &a, sizeof(a));
+ (void)ret;
+
+ sig_interrupt_notifier->setEnabled(true);
+
+ emit SigInterrupt();
+}
#endif // __linux__
void GMainWindow::PreventOSSleep() {
@@ -1284,6 +1530,7 @@ 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, // Photo Viewer
std::make_unique<QtProfileSelector>(*this), // Profile Selector
@@ -1357,23 +1604,24 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
}
return false;
}
- game_path = filename;
+ current_game_path = filename;
system->TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "Qt");
return true;
}
-void GMainWindow::SelectAndSetCurrentUser() {
+bool GMainWindow::SelectAndSetCurrentUser() {
QtProfileSelectionDialog dialog(system->HIDCore(), this);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
if (dialog.exec() == QDialog::Rejected) {
- return;
+ return false;
}
Settings::values.current_user = dialog.GetIndex();
+ return true;
}
void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index,
@@ -1391,16 +1639,15 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
if (loader != nullptr && loader->ReadProgramId(title_id) == Loader::ResultStatus::Success &&
type == StartGameType::Normal) {
// Load per game settings
- const auto file_path = std::filesystem::path{filename.toStdU16String()};
+ const auto file_path =
+ std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())};
const auto config_file_name = title_id == 0
? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", title_id);
- Config per_game_config(*system, config_file_name, Config::ConfigType::PerGameConfig);
+ Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
+ system->ApplySettings();
}
- // Disable fps limit toggle when booting a new title
- Settings::values.disable_fps_limit.SetValue(false);
-
// Save configurations
UpdateUISettings();
game_list->SaveInterfaceLayout();
@@ -1409,11 +1656,16 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
Settings::LogSettings();
if (UISettings::values.select_user_on_boot) {
- SelectAndSetCurrentUser();
+ if (SelectAndSetCurrentUser() == false) {
+ return;
+ }
}
- if (!LoadROM(filename, program_id, program_index))
+ if (!LoadROM(filename, program_id, program_index)) {
return;
+ }
+
+ system->SetShuttingDown(false);
// Create and start the emulation thread
emu_thread = std::make_unique<EmuThread>(*system);
@@ -1422,7 +1674,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
// Register an ExecuteProgram callback such that Core can execute a sub-program
system->RegisterExecuteProgramCallback(
- [this](std::size_t program_index) { render_window->ExecuteProgram(program_index); });
+ [this](std::size_t program_index_) { render_window->ExecuteProgram(program_index_); });
// Register an Exit callback such that Core can exit the currently running application.
system->RegisterExitCallback([this]() { render_window->Exit(); });
@@ -1457,6 +1709,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
mouse_hide_timer.start();
}
+ render_window->InitializeCamera();
+
std::string title_name;
std::string title_version;
const auto res = system->GetGameName(title_name);
@@ -1472,7 +1726,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
}
if (res != Loader::ResultStatus::Success || title_name.empty()) {
title_name = Common::FS::PathToUTF8String(
- std::filesystem::path{filename.toStdU16String()}.filename());
+ std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())}
+ .filename());
}
const bool is_64bit = system->Kernel().CurrentProcess()->Is64BitProcess();
const auto instruction_set_suffix = is_64bit ? tr("(64-bit)") : tr("(32-bit)");
@@ -1504,6 +1759,8 @@ void GMainWindow::ShutdownGame() {
AllowOSSleep();
+ system->SetShuttingDown(true);
+ system->DetachDebugger();
discord_rpc->Pause();
emu_thread->RequestStop();
@@ -1535,6 +1792,7 @@ void GMainWindow::ShutdownGame() {
tas_label->clear();
input_subsystem->GetTas()->Stop();
OnTasStateChanged();
+ render_window->FinalizeCamera();
// Enable all controllers
system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
@@ -1551,9 +1809,9 @@ void GMainWindow::ShutdownGame() {
emu_speed_label->setVisible(false);
game_fps_label->setVisible(false);
emu_frametime_label->setVisible(false);
- renderer_status_button->setEnabled(true);
+ renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan);
- game_path.clear();
+ current_game_path.clear();
// When closing the game, destroy the GLWindow to clear the context after the game is closed
render_window->ReleaseRenderTarget();
@@ -1571,7 +1829,7 @@ void GMainWindow::StoreRecentFile(const QString& filename) {
void GMainWindow::UpdateRecentFiles() {
const int num_recent_files =
- std::min(UISettings::values.recent_files.size(), max_recent_files_item);
+ std::min(static_cast<int>(UISettings::values.recent_files.size()), max_recent_files_item);
for (int i = 0; i < num_recent_files; i++) {
const QString text = QStringLiteral("&%1. %2").arg(i + 1).arg(
@@ -1746,7 +2004,7 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
}
void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) {
- const QString entry_type = [this, type] {
+ const QString entry_type = [type] {
switch (type) {
case InstalledEntryType::Game:
return tr("Contents");
@@ -1843,7 +2101,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type)
void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
const std::string& game_path) {
- const QString question = [this, target] {
+ const QString question = [target] {
switch (target) {
case GameListRemoveTarget::GlShaderCache:
return tr("Delete OpenGL Transferable Shader Cache?");
@@ -2472,7 +2730,7 @@ void GMainWindow::OnRestartGame() {
return;
}
// Make a copy since BootGame edits game_path
- BootGame(QString(game_path));
+ BootGame(QString(current_game_path));
}
void GMainWindow::OnPauseGame() {
@@ -2486,6 +2744,7 @@ void GMainWindow::OnPauseContinueGame() {
if (emu_thread->IsRunning()) {
OnPauseGame();
} else {
+ RequestGameResume();
OnStartGame();
}
}
@@ -2515,6 +2774,11 @@ void GMainWindow::OnExit() {
OnStopGame();
}
+void GMainWindow::OnSaveConfig() {
+ system->ApplySettings();
+ config->Save();
+}
+
void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) {
OverlayDialog dialog(render_window, *system, error_code, error_text, QString{}, tr("OK"),
Qt::AlignLeft | Qt::AlignVCenter);
@@ -2569,6 +2833,18 @@ void GMainWindow::ToggleFullscreen() {
}
}
+// We're going to return the screen that the given window has the most pixels on
+static QScreen* GuessCurrentScreen(QWidget* window) {
+ const QList<QScreen*> screens = QGuiApplication::screens();
+ return *std::max_element(
+ screens.cbegin(), screens.cend(), [window](const QScreen* left, const QScreen* right) {
+ const QSize left_size = left->geometry().intersected(window->geometry()).size();
+ const QSize right_size = right->geometry().intersected(window->geometry()).size();
+ return (left_size.height() * left_size.width()) <
+ (right_size.height() * right_size.width());
+ });
+}
+
void GMainWindow::ShowFullscreen() {
const auto show_fullscreen = [](QWidget* window) {
if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) {
@@ -2577,7 +2853,7 @@ void GMainWindow::ShowFullscreen() {
}
window->hide();
window->setWindowFlags(window->windowFlags() | Qt::FramelessWindowHint);
- const auto screen_geometry = QApplication::desktop()->screenGeometry(window);
+ const auto screen_geometry = GuessCurrentScreen(window)->geometry();
window->setGeometry(screen_geometry.x(), screen_geometry.y(), screen_geometry.width(),
screen_geometry.height() + 1);
window->raise();
@@ -2681,7 +2957,8 @@ void GMainWindow::OnConfigure() {
const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue();
Settings::SetConfiguringGlobal(true);
- ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system);
+ ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system,
+ !multiplayer_state->IsHostingPublicRoom());
connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this,
&GMainWindow::OnLanguageChanged);
@@ -2717,7 +2994,7 @@ void GMainWindow::OnConfigure() {
Settings::values.disabled_addons.clear();
- config = std::make_unique<Config>(*system);
+ config = std::make_unique<Config>();
UISettings::values.reset_to_defaults = false;
UISettings::values.game_dirs = std::move(old_game_dirs);
@@ -2738,6 +3015,11 @@ void GMainWindow::OnConfigure() {
if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) {
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
}
+
+ if (!multiplayer_state->IsHostingPublicRoom()) {
+ multiplayer_state->UpdateCredentials();
+ }
+
emit UpdateThemedIcons();
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
@@ -2761,8 +3043,19 @@ void GMainWindow::OnConfigure() {
mouse_hide_timer.start();
}
+ // Restart camera config
+ if (emulation_running) {
+ render_window->FinalizeCamera();
+ render_window->InitializeCamera();
+ }
+
+ if (!UISettings::values.has_broken_vulkan) {
+ renderer_status_button->setEnabled(!emulation_running);
+ }
+
UpdateStatusButtons();
controller_dialog->refreshConfiguration();
+ system->ApplySettings();
}
void GMainWindow::OnConfigureTas() {
@@ -2846,7 +3139,7 @@ void GMainWindow::OnToggleDockedMode() {
}
Settings::values.use_docked_mode.SetValue(!is_docked);
- dock_status_button->setChecked(!is_docked);
+ UpdateDockedButton();
OnDockedModeChanged(is_docked, !is_docked, *system);
}
@@ -2885,7 +3178,7 @@ void GMainWindow::OnToggleAdaptingFilter() {
void GMainWindow::OnConfigurePerGame() {
const u64 title_id = system->GetCurrentProcessProgramID();
- OpenPerGameConfiguration(title_id, game_path.toStdString());
+ OpenPerGameConfiguration(title_id, current_game_path.toStdString());
}
void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_name) {
@@ -2927,6 +3220,20 @@ void GMainWindow::OnLoadAmiibo() {
return;
}
+ auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
+
+ // Remove amiibo if one is connected
+ if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) {
+ virtual_amiibo->CloseAmiibo();
+ QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
+ return;
+ }
+
+ if (virtual_amiibo->GetCurrentState() != InputCommon::VirtualAmiibo::State::WaitingForAmiibo) {
+ QMessageBox::warning(this, tr("Error"), tr("The current game is not looking for amiibos"));
+ return;
+ }
+
is_amiibo_file_select_active = true;
const QString extensions{QStringLiteral("*.bin")};
const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
@@ -2941,34 +3248,30 @@ void GMainWindow::OnLoadAmiibo() {
}
void GMainWindow::LoadAmiibo(const QString& filename) {
- Service::SM::ServiceManager& sm = system->ServiceManager();
- auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user");
- if (nfc == nullptr) {
+ auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
+ const QString title = tr("Error loading Amiibo data");
+ // Remove amiibo if one is connected
+ if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) {
+ virtual_amiibo->CloseAmiibo();
+ QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
return;
}
- QFile nfc_file{filename};
- if (!nfc_file.open(QIODevice::ReadOnly)) {
- QMessageBox::warning(this, tr("Error opening Amiibo data file"),
- tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename));
- return;
- }
-
- const u64 nfc_file_size = nfc_file.size();
- std::vector<u8> buffer(nfc_file_size);
- const u64 read_size = nfc_file.read(reinterpret_cast<char*>(buffer.data()), nfc_file_size);
- if (nfc_file_size != read_size) {
- QMessageBox::warning(this, tr("Error reading Amiibo data file"),
- tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but "
- "was only able to read %2 bytes.")
- .arg(nfc_file_size)
- .arg(read_size));
- return;
- }
-
- if (!nfc->LoadAmiibo(buffer)) {
- QMessageBox::warning(this, tr("Error loading Amiibo data"),
- tr("Unable to load Amiibo data."));
+ switch (virtual_amiibo->LoadAmiibo(filename.toStdString())) {
+ case InputCommon::VirtualAmiibo::Info::NotAnAmiibo:
+ QMessageBox::warning(this, title, tr("The selected file is not a valid amiibo"));
+ break;
+ case InputCommon::VirtualAmiibo::Info::UnableToLoad:
+ QMessageBox::warning(this, title, tr("The selected file is already on use"));
+ break;
+ case InputCommon::VirtualAmiibo::Info::WrongDeviceState:
+ QMessageBox::warning(this, title, tr("The current game is not looking for amiibos"));
+ break;
+ case InputCommon::VirtualAmiibo::Info::Unknown:
+ QMessageBox::warning(this, title, tr("An unkown error occured"));
+ break;
+ default:
+ break;
}
}
@@ -3046,7 +3349,8 @@ void GMainWindow::MigrateConfigFiles() {
}
const auto origin = config_dir_fs_path / filename;
const auto destination = config_dir_fs_path / "custom" / filename;
- LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination);
+ LOG_INFO(Frontend, "Migrating config file from {} to {}", origin.string(),
+ destination.string());
if (!Common::FS::RenameFile(origin, destination)) {
// Delete the old config file if one already exists in the new location.
Common::FS::RemoveFile(origin);
@@ -3112,7 +3416,7 @@ void GMainWindow::OnTasStateChanged() {
}
void GMainWindow::UpdateStatusBar() {
- if (emu_thread == nullptr) {
+ if (emu_thread == nullptr || !system->IsPoweredOn()) {
status_bar_update_timer.stop();
return;
}
@@ -3146,11 +3450,12 @@ void GMainWindow::UpdateStatusBar() {
} else {
emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
}
- if (Settings::values.disable_fps_limit) {
+ if (!Settings::values.use_speed_limit) {
game_fps_label->setText(
- tr("Game: %1 FPS (Unlocked)").arg(results.average_game_fps, 0, 'f', 0));
+ tr("Game: %1 FPS (Unlocked)").arg(std::round(results.average_game_fps), 0, 'f', 0));
} else {
- game_fps_label->setText(tr("Game: %1 FPS").arg(results.average_game_fps, 0, 'f', 0));
+ game_fps_label->setText(
+ tr("Game: %1 FPS").arg(std::round(results.average_game_fps), 0, 'f', 0));
}
emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
@@ -3184,6 +3489,12 @@ void GMainWindow::UpdateGPUAccuracyButton() {
}
}
+void GMainWindow::UpdateDockedButton() {
+ const bool is_docked = Settings::values.use_docked_mode.GetValue();
+ dock_status_button->setChecked(is_docked);
+ dock_status_button->setText(is_docked ? tr("DOCKED") : tr("HANDHELD"));
+}
+
void GMainWindow::UpdateFilterText() {
const auto filter = Settings::values.scaling_filter.GetValue();
switch (filter) {
@@ -3227,10 +3538,10 @@ void GMainWindow::UpdateAAText() {
}
void GMainWindow::UpdateStatusButtons() {
- dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() ==
Settings::RendererBackend::Vulkan);
UpdateGPUAccuracyButton();
+ UpdateDockedButton();
UpdateFilterText();
UpdateAAText();
}
@@ -3269,10 +3580,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(QPoint{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) {
@@ -3498,6 +3825,8 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
}
render_window->close();
+ multiplayer_state->Close();
+ system->GetRoomNetwork().Shutdown();
QWidget::closeEvent(event);
}
@@ -3545,6 +3874,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;
@@ -3583,13 +3928,41 @@ void GMainWindow::RequestGameExit() {
}
}
+void GMainWindow::RequestGameResume() {
+ auto& sm{system->ServiceManager()};
+ auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
+ auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
+
+ if (applet_oe != nullptr) {
+ applet_oe->GetMessageQueue()->RequestResume();
+ return;
+ }
+
+ if (applet_ae != nullptr) {
+ applet_ae->GetMessageQueue()->RequestResume();
+ }
+}
+
void GMainWindow::filterBarSetChecked(bool state) {
ui->action_Show_Filter_Bar->setChecked(state);
emit(OnToggleFilterBar());
}
+static void AdjustLinkColor() {
+ 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));
+ }
+ if (qApp->palette().color(QPalette::Link) != new_pal.color(QPalette::Link)) {
+ qApp->setPalette(new_pal);
+ }
+}
+
void GMainWindow::UpdateUITheme() {
- const QString default_theme = QStringLiteral("default");
+ const QString default_theme =
+ QString::fromUtf8(UISettings::themes[static_cast<size_t>(Config::default_theme)].second);
QString current_theme = UISettings::values.theme;
QStringList theme_paths(default_theme_paths);
@@ -3597,6 +3970,23 @@ void GMainWindow::UpdateUITheme() {
current_theme = default_theme;
}
+#ifdef _WIN32
+ QIcon::setThemeName(current_theme);
+ AdjustLinkColor();
+#else
+ if (current_theme == QStringLiteral("default") || current_theme == QStringLiteral("colorful")) {
+ QIcon::setThemeName(current_theme == QStringLiteral("colorful") ? current_theme
+ : startup_icon_theme);
+ QIcon::setThemeSearchPaths(theme_paths);
+ if (CheckDarkMode()) {
+ current_theme = QStringLiteral("default_dark");
+ }
+ } else {
+ QIcon::setThemeName(current_theme);
+ QIcon::setThemeSearchPaths(QStringList(QStringLiteral(":/icons")));
+ AdjustLinkColor();
+ }
+#endif
if (current_theme != default_theme) {
QString theme_uri{QStringLiteral(":%1/style.qss").arg(current_theme)};
QFile f(theme_uri);
@@ -3619,17 +4009,9 @@ void GMainWindow::UpdateUITheme() {
qApp->setStyleSheet({});
setStyleSheet({});
}
-
- QIcon::setThemeName(current_theme);
- QIcon::setThemeSearchPaths(theme_paths);
}
void GMainWindow::LoadTranslation() {
- // If the selected language is English, no need to install any translation
- if (UISettings::values.language == QStringLiteral("en")) {
- return;
- }
-
bool loaded;
if (UISettings::values.language.isEmpty()) {
@@ -3655,6 +4037,7 @@ void GMainWindow::OnLanguageChanged(const QString& locale) {
UISettings::values.language = locale;
LoadTranslation();
ui->retranslateUi(this);
+ multiplayer_state->retranslateUi();
UpdateWindowTitle();
}
@@ -3671,11 +4054,54 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
discord_rpc->Update();
}
+void GMainWindow::changeEvent(QEvent* event) {
+#ifdef __linux__
+ // PaletteChange event appears to only reach so far into the GUI, explicitly asking to
+ // UpdateUITheme is a decent work around
+ if (event->type() == QEvent::PaletteChange) {
+ const QPalette test_palette(qApp->palette());
+ const QString current_theme = UISettings::values.theme;
+ // Keeping eye on QPalette::Window to avoid looping. QPalette::Text might be useful too
+ static QColor last_window_color;
+ const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window);
+ if (last_window_color != window_color && (current_theme == QStringLiteral("default") ||
+ current_theme == QStringLiteral("colorful"))) {
+ UpdateUITheme();
+ }
+ last_window_color = window_color;
+ }
+#endif // __linux__
+ QWidget::changeEvent(event);
+}
+
#ifdef main
#undef main
#endif
int main(int argc, char* argv[]) {
+ std::unique_ptr<Config> config = std::make_unique<Config>();
+ bool has_broken_vulkan = false;
+ bool is_child = false;
+ if (CheckEnvVars(&is_child)) {
+ return 0;
+ }
+
+#ifdef YUZU_DBGHELP
+ PROCESS_INFORMATION pi;
+ if (!is_child && Settings::values.create_crash_dumps.GetValue() &&
+ MiniDump::SpawnDebuggee(argv[0], pi)) {
+ // Delete the config object so that it doesn't save when the program exits
+ config.reset(nullptr);
+ MiniDump::DebugDebuggee(pi);
+ return 0;
+ }
+#endif
+
+ if (StartupChecks(argv[0], &has_broken_vulkan,
+ Settings::values.perform_vulkan_check.GetValue())) {
+ return 0;
+ }
+
Common::DetachedTasks detached_tasks;
MicroProfileOnThreadCreate("Frontend");
SCOPE_EXIT({ MicroProfileShutdown(); });
@@ -3711,11 +4137,20 @@ int main(int argc, char* argv[]) {
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
QApplication app(argc, argv);
+ // Workaround for QTBUG-85409, for Suzhou numerals the number 1 is actually \u3021
+ // so we can see if we get \u3008 instead
+ // TL;DR all other number formats are consecutive in unicode code points
+ // This bug is fixed in Qt6, specifically 6.0.0-alpha1
+ const QLocale locale = QLocale::system();
+ if (QStringLiteral("\u3008") == locale.toString(1)) {
+ QLocale::setDefault(QLocale::system().name());
+ }
+
// Qt changes the locale and causes issues in float conversion using std::to_string() when
// generating shaders
setlocale(LC_ALL, "C");
- GMainWindow main_window{};
+ GMainWindow main_window{std::move(config), has_broken_vulkan};
// After settings have been loaded by GMainWindow, apply the filter
main_window.show();
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 6a35b9e3d..f7aa8e417 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -1,19 +1,17 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <optional>
-#include <unordered_map>
#include <QMainWindow>
#include <QTimer>
#include <QTranslator>
+#include "common/announce_multiplayer_room.h"
#include "common/common_types.h"
-#include "core/hle/service/acc/profile_manager.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h"
@@ -24,6 +22,7 @@
#endif
class Config;
+class ClickableLabel;
class EmuThread;
class GameList;
class GImageInfo;
@@ -33,6 +32,7 @@ class MicroProfileDialog;
class ProfilerWidget;
class ControllerDialog;
class QLabel;
+class MultiplayerState;
class QPushButton;
class QProgressDialog;
class WaitTreeWidget;
@@ -120,7 +120,7 @@ class GMainWindow : public QMainWindow {
public:
void filterBarSetChecked(bool state);
void UpdateUITheme();
- explicit GMainWindow();
+ explicit GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan);
~GMainWindow() override;
bool DropAction(QDropEvent* event);
@@ -163,10 +163,13 @@ signals:
void WebBrowserExtractOfflineRomFS();
void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url);
+ void SigInterrupt();
+
public slots:
void OnLoadComplete();
void OnExecuteProgram(std::size_t program_index);
void OnExit();
+ void OnSaveConfig();
void ControllerSelectorReconfigureControllers(
const Core::Frontend::ControllerParameters& parameters);
void SoftwareKeyboardInitialize(
@@ -202,6 +205,8 @@ private:
void ConnectMenuEvents();
void UpdateMenuState();
+ void SetupPrepareForSleep();
+
void PreventOSSleep();
void AllowOSSleep();
@@ -214,7 +219,7 @@ private:
void SetDiscordEnabled(bool state);
void LoadAmiibo(const QString& filename);
- void SelectAndSetCurrentUser();
+ bool SelectAndSetCurrentUser();
/**
* Stores the filename in the recently loaded files list.
@@ -246,14 +251,23 @@ private:
bool ConfirmChangeGame();
bool ConfirmForceLockedExit();
void RequestGameExit();
+ void RequestGameResume();
+ void changeEvent(QEvent* event) override;
void closeEvent(QCloseEvent* event) override;
+#ifdef __linux__
+ void SetupSigInterrupts();
+ static void HandleSigInterrupt(int);
+ void OnSigInterruptNotifierActivated();
+#endif
+
private slots:
void OnStartGame();
void OnRestartGame();
void OnPauseGame();
void OnPauseContinueGame();
void OnStopGame();
+ void OnPrepareForSleep(bool prepare_sleep);
void OnMenuReportCompatibility();
void OnOpenModsPage();
void OnOpenQuickstartGuide();
@@ -322,6 +336,7 @@ private:
void MigrateConfigFiles();
void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
std::string_view gpu_vendor = {});
+ void UpdateDockedButton();
void UpdateFilterText();
void UpdateAAText();
void UpdateStatusBar();
@@ -330,9 +345,11 @@ 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);
+ bool CheckDarkMode();
QString GetTasStateDescription() const;
@@ -342,6 +359,8 @@ private:
std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
+ MultiplayerState* multiplayer_state = nullptr;
+
GRenderWindow* render_window;
GameList* game_list;
LoadingScreen* loading_screen;
@@ -369,11 +388,15 @@ private:
bool emulation_running = false;
std::unique_ptr<EmuThread> emu_thread;
// The path to the game currently running
- QString game_path;
+ QString current_game_path;
bool auto_paused = false;
bool auto_muted = false;
QTimer mouse_hide_timer;
+ QTimer mouse_center_timer;
+
+ QString startup_icon_theme;
+ bool os_dark_mode = false;
// FS
std::shared_ptr<FileSys::VfsFilesystem> vfs;
@@ -400,9 +423,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;
@@ -416,6 +436,9 @@ private:
bool is_tas_recording_dialog_active{};
#ifdef __linux__
+ QSocketNotifier* sig_interrupt_notifier;
+ static std::array<int, 3> sig_interrupt_fds;
+
QDBusObjectPath wake_lock{};
#endif
@@ -423,4 +446,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/main.ui b/src/yuzu/main.ui
index 5719b2ee4..74d49dbd4 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -120,6 +120,20 @@
<addaction name="menu_Reset_Window_Size"/>
<addaction name="menu_View_Debugging"/>
</widget>
+ <widget class="QMenu" name="menu_Multiplayer">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>&amp;Multiplayer</string>
+ </property>
+ <addaction name="action_View_Lobby"/>
+ <addaction name="action_Start_Room"/>
+ <addaction name="action_Connect_To_Room"/>
+ <addaction name="separator"/>
+ <addaction name="action_Show_Room"/>
+ <addaction name="action_Leave_Room"/>
+ </widget>
<widget class="QMenu" name="menu_Tools">
<property name="title">
<string>&amp;Tools</string>
@@ -154,6 +168,7 @@
<addaction name="menu_Emulation"/>
<addaction name="menu_View"/>
<addaction name="menu_Tools"/>
+ <addaction name="menu_Multiplayer"/>
<addaction name="menu_Help"/>
</widget>
<action name="action_Install_File_NAND">
@@ -245,6 +260,43 @@
<string>Show Status Bar</string>
</property>
</action>
+ <action name="action_View_Lobby">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>&amp;Browse Public Game Lobby</string>
+ </property>
+ </action>
+ <action name="action_Start_Room">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>&amp;Create Room</string>
+ </property>
+ </action>
+ <action name="action_Leave_Room">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&amp;Leave Room</string>
+ </property>
+ </action>
+ <action name="action_Connect_To_Room">
+ <property name="text">
+ <string>&amp;Direct Connect to Room</string>
+ </property>
+ </action>
+ <action name="action_Show_Room">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&amp;Show Current Room</string>
+ </property>
+ </action>
<action name="action_Fullscreen">
<property name="checkable">
<bool>true</bool>
@@ -266,7 +318,7 @@
<bool>false</bool>
</property>
<property name="text">
- <string>Load &amp;Amiibo...</string>
+ <string>Load/Remove &amp;Amiibo...</string>
</property>
</action>
<action name="action_Report_Compatibility">
diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp
new file mode 100644
index 000000000..a34dc6a9c
--- /dev/null
+++ b/src/yuzu/mini_dump.cpp
@@ -0,0 +1,202 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <cstdio>
+#include <cstring>
+#include <ctime>
+#include <filesystem>
+#include <fmt/format.h>
+#include <windows.h>
+#include "yuzu/mini_dump.h"
+#include "yuzu/startup_checks.h"
+
+// dbghelp.h must be included after windows.h
+#include <dbghelp.h>
+
+namespace MiniDump {
+
+void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
+ EXCEPTION_POINTERS* pep) {
+ char file_name[255];
+ const std::time_t the_time = std::time(nullptr);
+ std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time));
+
+ // Open the file
+ HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
+ CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
+
+ if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) {
+ fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError());
+ return;
+ }
+
+ // Create the minidump
+ const MINIDUMP_TYPE dump_type = MiniDumpNormal;
+
+ const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle,
+ dump_type, (pep != 0) ? info : 0, 0, 0);
+
+ if (write_dump_status) {
+ fmt::print(stderr, "MiniDump created: {}", file_name);
+ } else {
+ fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError());
+ }
+
+ // Close the file
+ CloseHandle(file_handle);
+}
+
+void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) {
+ EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
+
+ HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId);
+ if (thread_handle == nullptr) {
+ fmt::print(stderr, "OpenThread failed ({})", GetLastError());
+ return;
+ }
+
+ // Get child process context
+ CONTEXT context = {};
+ context.ContextFlags = CONTEXT_ALL;
+ if (!GetThreadContext(thread_handle, &context)) {
+ fmt::print(stderr, "GetThreadContext failed ({})", GetLastError());
+ return;
+ }
+
+ // Create exception pointers for minidump
+ EXCEPTION_POINTERS ep;
+ ep.ExceptionRecord = &record;
+ ep.ContextRecord = &context;
+
+ MINIDUMP_EXCEPTION_INFORMATION info;
+ info.ThreadId = deb_ev.dwThreadId;
+ info.ExceptionPointers = &ep;
+ info.ClientPointers = false;
+
+ CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep);
+
+ if (CloseHandle(thread_handle) == 0) {
+ fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError());
+ }
+}
+
+bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) {
+ std::memset(&pi, 0, sizeof(pi));
+
+ // Don't debug if we are already being debugged
+ if (IsDebuggerPresent()) {
+ return false;
+ }
+
+ if (!SpawnChild(arg0, &pi, 0)) {
+ fmt::print(stderr, "warning: continuing without crash dumps");
+ return false;
+ }
+
+ const bool can_debug = DebugActiveProcess(pi.dwProcessId);
+ if (!can_debug) {
+ fmt::print(stderr,
+ "warning: DebugActiveProcess failed ({}), continuing without crash dumps",
+ GetLastError());
+ return false;
+ }
+
+ return true;
+}
+
+static const char* ExceptionName(DWORD exception) {
+ switch (exception) {
+ case EXCEPTION_ACCESS_VIOLATION:
+ return "EXCEPTION_ACCESS_VIOLATION";
+ case EXCEPTION_DATATYPE_MISALIGNMENT:
+ return "EXCEPTION_DATATYPE_MISALIGNMENT";
+ case EXCEPTION_BREAKPOINT:
+ return "EXCEPTION_BREAKPOINT";
+ case EXCEPTION_SINGLE_STEP:
+ return "EXCEPTION_SINGLE_STEP";
+ case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
+ return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
+ case EXCEPTION_FLT_DENORMAL_OPERAND:
+ return "EXCEPTION_FLT_DENORMAL_OPERAND";
+ case EXCEPTION_FLT_DIVIDE_BY_ZERO:
+ return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
+ case EXCEPTION_FLT_INEXACT_RESULT:
+ return "EXCEPTION_FLT_INEXACT_RESULT";
+ case EXCEPTION_FLT_INVALID_OPERATION:
+ return "EXCEPTION_FLT_INVALID_OPERATION";
+ case EXCEPTION_FLT_OVERFLOW:
+ return "EXCEPTION_FLT_OVERFLOW";
+ case EXCEPTION_FLT_STACK_CHECK:
+ return "EXCEPTION_FLT_STACK_CHECK";
+ case EXCEPTION_FLT_UNDERFLOW:
+ return "EXCEPTION_FLT_UNDERFLOW";
+ case EXCEPTION_INT_DIVIDE_BY_ZERO:
+ return "EXCEPTION_INT_DIVIDE_BY_ZERO";
+ case EXCEPTION_INT_OVERFLOW:
+ return "EXCEPTION_INT_OVERFLOW";
+ case EXCEPTION_PRIV_INSTRUCTION:
+ return "EXCEPTION_PRIV_INSTRUCTION";
+ case EXCEPTION_IN_PAGE_ERROR:
+ return "EXCEPTION_IN_PAGE_ERROR";
+ case EXCEPTION_ILLEGAL_INSTRUCTION:
+ return "EXCEPTION_ILLEGAL_INSTRUCTION";
+ case EXCEPTION_NONCONTINUABLE_EXCEPTION:
+ return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
+ case EXCEPTION_STACK_OVERFLOW:
+ return "EXCEPTION_STACK_OVERFLOW";
+ case EXCEPTION_INVALID_DISPOSITION:
+ return "EXCEPTION_INVALID_DISPOSITION";
+ case EXCEPTION_GUARD_PAGE:
+ return "EXCEPTION_GUARD_PAGE";
+ case EXCEPTION_INVALID_HANDLE:
+ return "EXCEPTION_INVALID_HANDLE";
+ default:
+ return "unknown exception type";
+ }
+}
+
+void DebugDebuggee(PROCESS_INFORMATION& pi) {
+ DEBUG_EVENT deb_ev = {};
+
+ while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) {
+ const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE);
+ if (!wait_success) {
+ fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError());
+ return;
+ }
+
+ switch (deb_ev.dwDebugEventCode) {
+ case OUTPUT_DEBUG_STRING_EVENT:
+ case CREATE_PROCESS_DEBUG_EVENT:
+ case CREATE_THREAD_DEBUG_EVENT:
+ case EXIT_PROCESS_DEBUG_EVENT:
+ case EXIT_THREAD_DEBUG_EVENT:
+ case LOAD_DLL_DEBUG_EVENT:
+ case RIP_EVENT:
+ case UNLOAD_DLL_DEBUG_EVENT:
+ // Continue on all other debug events
+ ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE);
+ break;
+ case EXCEPTION_DEBUG_EVENT:
+ EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
+
+ // We want to generate a crash dump if we are seeing the same exception again.
+ if (!deb_ev.u.Exception.dwFirstChance) {
+ fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n",
+ record.ExceptionCode, ExceptionName(record.ExceptionCode));
+ DumpFromDebugEvent(deb_ev, pi);
+ }
+
+ // Continue without handling the exception.
+ // Lets the debuggee use its own exception handler.
+ // - If one does not exist, we will see the exception once more where we make a minidump
+ // for. Then when it reaches here again, yuzu will probably crash.
+ // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an
+ // infinite loop of exceptions.
+ ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
+ break;
+ }
+ }
+}
+
+} // namespace MiniDump
diff --git a/src/yuzu/mini_dump.h b/src/yuzu/mini_dump.h
new file mode 100644
index 000000000..d6b6cca84
--- /dev/null
+++ b/src/yuzu/mini_dump.h
@@ -0,0 +1,19 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <windows.h>
+
+#include <dbghelp.h>
+
+namespace MiniDump {
+
+void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
+ EXCEPTION_POINTERS* pep);
+
+void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi);
+bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi);
+void DebugDebuggee(PROCESS_INFORMATION& pi);
+
+} // namespace MiniDump
diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp
new file mode 100644
index 000000000..dec9696c1
--- /dev/null
+++ b/src/yuzu/multiplayer/chat_room.cpp
@@ -0,0 +1,508 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <future>
+#include <QColor>
+#include <QDesktopServices>
+#include <QFutureWatcher>
+#include <QImage>
+#include <QList>
+#include <QLocale>
+#include <QMenu>
+#include <QMessageBox>
+#include <QMetaType>
+#include <QTime>
+#include <QUrl>
+#include <QtConcurrent/QtConcurrentRun>
+#include "common/logging/log.h"
+#include "network/announce_multiplayer_session.h"
+#include "ui_chat_room.h"
+#include "yuzu/game_list_p.h"
+#include "yuzu/multiplayer/chat_room.h"
+#include "yuzu/multiplayer/message.h"
+#ifdef ENABLE_WEB_SERVICE
+#include "web_service/web_backend.h"
+#endif
+
+class ChatMessage {
+public:
+ explicit ChatMessage(const Network::ChatEntry& chat, Network::RoomNetwork& room_network,
+ QTime ts = {}) {
+ /// Convert the time to their default locale defined format
+ QLocale locale;
+ timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat);
+ nickname = QString::fromStdString(chat.nickname);
+ username = QString::fromStdString(chat.username);
+ message = QString::fromStdString(chat.message);
+
+ // Check for user pings
+ QString cur_nickname, cur_username;
+ if (auto room = room_network.GetRoomMember().lock()) {
+ cur_nickname = QString::fromStdString(room->GetNickname());
+ cur_username = QString::fromStdString(room->GetUsername());
+ }
+
+ // Handle pings at the beginning and end of message
+ QString fixed_message = QStringLiteral(" %1 ").arg(message);
+ if (fixed_message.contains(QStringLiteral(" @%1 ").arg(cur_nickname)) ||
+ (!cur_username.isEmpty() &&
+ fixed_message.contains(QStringLiteral(" @%1 ").arg(cur_username)))) {
+
+ contains_ping = true;
+ } else {
+ contains_ping = false;
+ }
+ }
+
+ bool ContainsPing() const {
+ return contains_ping;
+ }
+
+ /// Format the message using the players color
+ QString GetPlayerChatMessage(u16 player) const {
+ const bool is_dark_theme = QIcon::themeName().contains(QStringLiteral("dark")) ||
+ QIcon::themeName().contains(QStringLiteral("midnight"));
+ auto color =
+ is_dark_theme ? player_color_dark[player % 16] : player_color_default[player % 16];
+ QString name;
+ if (username.isEmpty() || username == nickname) {
+ name = nickname;
+ } else {
+ name = QStringLiteral("%1 (%2)").arg(nickname, username);
+ }
+
+ QString style, text_color;
+ if (ContainsPing()) {
+ // Add a background color to these messages
+ style = QStringLiteral("background-color: %1").arg(QString::fromStdString(ping_color));
+ // Add a font color
+ text_color = QStringLiteral("color='#000000'");
+ }
+
+ return QStringLiteral("[%1] <font color='%2'>&lt;%3&gt;</font> <font style='%4' "
+ "%5>%6</font>")
+ .arg(timestamp, QString::fromStdString(color), name.toHtmlEscaped(), style, text_color,
+ message.toHtmlEscaped());
+ }
+
+private:
+ static constexpr std::array<const char*, 16> player_color_default = {
+ {"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222",
+ "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "#FFFF00"}};
+ static constexpr std::array<const char*, 16> player_color_dark = {
+ {"#559AD1", "#4EC9A8", "#D69D85", "#C6C923", "#B975B5", "#D81F1F", "#7EAE39", "#4F8733",
+ "#F7CD8A", "#6FCACF", "#CE4897", "#8A2BE2", "#D2691E", "#9ACD32", "#FF7F50", "#152ccd"}};
+ static constexpr char ping_color[] = "#FFFF00";
+
+ QString timestamp;
+ QString nickname;
+ QString username;
+ QString message;
+ bool contains_ping;
+};
+
+class StatusMessage {
+public:
+ explicit StatusMessage(const QString& msg, QTime ts = {}) {
+ /// Convert the time to their default locale defined format
+ QLocale locale;
+ timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat);
+ message = msg;
+ }
+
+ QString GetSystemChatMessage() const {
+ return QStringLiteral("[%1] <font color='%2'>* %3</font>")
+ .arg(timestamp, QString::fromStdString(system_color), message);
+ }
+
+private:
+ static constexpr const char system_color[] = "#FF8C00";
+ QString timestamp;
+ QString message;
+};
+
+class PlayerListItem : public QStandardItem {
+public:
+ static const int NicknameRole = Qt::UserRole + 1;
+ static const int UsernameRole = Qt::UserRole + 2;
+ static const int AvatarUrlRole = Qt::UserRole + 3;
+ static const int GameNameRole = Qt::UserRole + 4;
+ static const int GameVersionRole = Qt::UserRole + 5;
+
+ PlayerListItem() = default;
+ explicit PlayerListItem(const std::string& nickname, const std::string& username,
+ const std::string& avatar_url,
+ const AnnounceMultiplayerRoom::GameInfo& game_info) {
+ setEditable(false);
+ setData(QString::fromStdString(nickname), NicknameRole);
+ setData(QString::fromStdString(username), UsernameRole);
+ setData(QString::fromStdString(avatar_url), AvatarUrlRole);
+ if (game_info.name.empty()) {
+ setData(QObject::tr("Not playing a game"), GameNameRole);
+ } else {
+ setData(QString::fromStdString(game_info.name), GameNameRole);
+ }
+ setData(QString::fromStdString(game_info.version), GameVersionRole);
+ }
+
+ QVariant data(int role) const override {
+ if (role != Qt::DisplayRole) {
+ return QStandardItem::data(role);
+ }
+ QString name;
+ const QString nickname = data(NicknameRole).toString();
+ const QString username = data(UsernameRole).toString();
+ if (username.isEmpty() || username == nickname) {
+ name = nickname;
+ } else {
+ name = QStringLiteral("%1 (%2)").arg(nickname, username);
+ }
+ const QString version = data(GameVersionRole).toString();
+ QString version_string;
+ if (!version.isEmpty()) {
+ version_string = QStringLiteral("(%1)").arg(version);
+ }
+ return QStringLiteral("%1\n %2 %3")
+ .arg(name, data(GameNameRole).toString(), version_string);
+ }
+};
+
+ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ChatRoom>()) {
+ ui->setupUi(this);
+
+ // set the item_model for player_view
+
+ player_list = new QStandardItemModel(ui->player_view);
+ ui->player_view->setModel(player_list);
+ ui->player_view->setContextMenuPolicy(Qt::CustomContextMenu);
+ // set a header to make it look better though there is only one column
+ player_list->insertColumns(0, 1);
+ player_list->setHeaderData(0, Qt::Horizontal, tr("Members"));
+
+ ui->chat_history->document()->setMaximumBlockCount(max_chat_lines);
+
+ auto font = ui->chat_history->font();
+ font.setPointSizeF(10);
+ ui->chat_history->setFont(font);
+
+ // register the network structs to use in slots and signals
+ qRegisterMetaType<Network::ChatEntry>();
+ qRegisterMetaType<Network::StatusMessageEntry>();
+ qRegisterMetaType<Network::RoomInformation>();
+ qRegisterMetaType<Network::RoomMember::State>();
+
+ // Connect all the widgets to the appropriate events
+ connect(ui->player_view, &QTreeView::customContextMenuRequested, this,
+ &ChatRoom::PopupContextMenu);
+ connect(ui->chat_message, &QLineEdit::returnPressed, this, &ChatRoom::OnSendChat);
+ connect(ui->chat_message, &QLineEdit::textChanged, this, &ChatRoom::OnChatTextChanged);
+ connect(ui->send_message, &QPushButton::clicked, this, &ChatRoom::OnSendChat);
+}
+
+ChatRoom::~ChatRoom() = default;
+
+void ChatRoom::Initialize(Network::RoomNetwork* room_network_) {
+ room_network = room_network_;
+ // setup the callbacks for network updates
+ if (auto member = room_network->GetRoomMember().lock()) {
+ member->BindOnChatMessageRecieved(
+ [this](const Network::ChatEntry& chat) { emit ChatReceived(chat); });
+ member->BindOnStatusMessageReceived(
+ [this](const Network::StatusMessageEntry& status_message) {
+ emit StatusMessageReceived(status_message);
+ });
+ connect(this, &ChatRoom::ChatReceived, this, &ChatRoom::OnChatReceive);
+ connect(this, &ChatRoom::StatusMessageReceived, this, &ChatRoom::OnStatusMessageReceive);
+ }
+}
+
+void ChatRoom::SetModPerms(bool is_mod) {
+ has_mod_perms = is_mod;
+}
+
+void ChatRoom::RetranslateUi() {
+ ui->retranslateUi(this);
+}
+
+void ChatRoom::Clear() {
+ ui->chat_history->clear();
+ block_list.clear();
+}
+
+void ChatRoom::AppendStatusMessage(const QString& msg) {
+ ui->chat_history->append(StatusMessage(msg).GetSystemChatMessage());
+}
+
+void ChatRoom::AppendChatMessage(const QString& msg) {
+ ui->chat_history->append(msg);
+}
+
+void ChatRoom::SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname) {
+ if (auto room = room_network->GetRoomMember().lock()) {
+ auto members = room->GetMemberInformation();
+ auto it = std::find_if(members.begin(), members.end(),
+ [&nickname](const Network::RoomMember::MemberInformation& member) {
+ return member.nickname == nickname;
+ });
+ if (it == members.end()) {
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::NO_SUCH_USER);
+ return;
+ }
+ room->SendModerationRequest(type, nickname);
+ }
+}
+
+bool ChatRoom::ValidateMessage(const std::string& msg) {
+ return !msg.empty();
+}
+
+void ChatRoom::OnRoomUpdate(const Network::RoomInformation& info) {
+ // TODO(B3N30): change title
+ if (auto room_member = room_network->GetRoomMember().lock()) {
+ SetPlayerList(room_member->GetMemberInformation());
+ }
+}
+
+void ChatRoom::Disable() {
+ ui->send_message->setDisabled(true);
+ ui->chat_message->setDisabled(true);
+}
+
+void ChatRoom::Enable() {
+ ui->send_message->setEnabled(true);
+ ui->chat_message->setEnabled(true);
+}
+
+void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) {
+ if (!ValidateMessage(chat.message)) {
+ return;
+ }
+ if (auto room = room_network->GetRoomMember().lock()) {
+ // get the id of the player
+ auto members = room->GetMemberInformation();
+ auto it = std::find_if(members.begin(), members.end(),
+ [&chat](const Network::RoomMember::MemberInformation& member) {
+ return member.nickname == chat.nickname &&
+ member.username == chat.username;
+ });
+ if (it == members.end()) {
+ LOG_INFO(Network, "Chat message received from unknown player. Ignoring it.");
+ return;
+ }
+ if (block_list.count(chat.nickname)) {
+ LOG_INFO(Network, "Chat message received from blocked player {}. Ignoring it.",
+ chat.nickname);
+ return;
+ }
+ auto player = std::distance(members.begin(), it);
+ ChatMessage m(chat, *room_network);
+ if (m.ContainsPing()) {
+ emit UserPinged();
+ }
+ AppendChatMessage(m.GetPlayerChatMessage(player));
+ }
+}
+
+void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_message) {
+ QString name;
+ if (status_message.username.empty() || status_message.username == status_message.nickname) {
+ name = QString::fromStdString(status_message.nickname);
+ } else {
+ name = QStringLiteral("%1 (%2)").arg(QString::fromStdString(status_message.nickname),
+ QString::fromStdString(status_message.username));
+ }
+ QString message;
+ switch (status_message.type) {
+ case Network::IdMemberJoin:
+ message = tr("%1 has joined").arg(name);
+ break;
+ case Network::IdMemberLeave:
+ message = tr("%1 has left").arg(name);
+ break;
+ case Network::IdMemberKicked:
+ message = tr("%1 has been kicked").arg(name);
+ break;
+ case Network::IdMemberBanned:
+ message = tr("%1 has been banned").arg(name);
+ break;
+ case Network::IdAddressUnbanned:
+ message = tr("%1 has been unbanned").arg(name);
+ break;
+ }
+ if (!message.isEmpty())
+ AppendStatusMessage(message);
+}
+
+void ChatRoom::OnSendChat() {
+ if (auto room_member = room_network->GetRoomMember().lock()) {
+ if (!room_member->IsConnected()) {
+ return;
+ }
+ auto message = ui->chat_message->text().toStdString();
+ if (!ValidateMessage(message)) {
+ return;
+ }
+ auto nick = room_member->GetNickname();
+ auto username = room_member->GetUsername();
+ Network::ChatEntry chat{nick, username, message};
+
+ auto members = room_member->GetMemberInformation();
+ auto it = std::find_if(members.begin(), members.end(),
+ [&chat](const Network::RoomMember::MemberInformation& member) {
+ return member.nickname == chat.nickname &&
+ member.username == chat.username;
+ });
+ if (it == members.end()) {
+ LOG_INFO(Network, "Cannot find self in the player list when sending a message.");
+ }
+ auto player = std::distance(members.begin(), it);
+ ChatMessage m(chat, *room_network);
+ room_member->SendChatMessage(message);
+ AppendChatMessage(m.GetPlayerChatMessage(player));
+ ui->chat_message->clear();
+ }
+}
+
+void ChatRoom::UpdateIconDisplay() {
+ for (int row = 0; row < player_list->invisibleRootItem()->rowCount(); ++row) {
+ QStandardItem* item = player_list->invisibleRootItem()->child(row);
+ const std::string avatar_url =
+ item->data(PlayerListItem::AvatarUrlRole).toString().toStdString();
+ if (icon_cache.count(avatar_url)) {
+ item->setData(icon_cache.at(avatar_url), Qt::DecorationRole);
+ } else {
+ item->setData(QIcon::fromTheme(QStringLiteral("no_avatar")).pixmap(48),
+ Qt::DecorationRole);
+ }
+ }
+}
+
+void ChatRoom::SetPlayerList(const Network::RoomMember::MemberList& member_list) {
+ // TODO(B3N30): Remember which row is selected
+ player_list->removeRows(0, player_list->rowCount());
+ for (const auto& member : member_list) {
+ if (member.nickname.empty())
+ continue;
+ QStandardItem* name_item = new PlayerListItem(member.nickname, member.username,
+ member.avatar_url, member.game_info);
+
+#ifdef ENABLE_WEB_SERVICE
+ if (!icon_cache.count(member.avatar_url) && !member.avatar_url.empty()) {
+ // Start a request to get the member's avatar
+ const QUrl url(QString::fromStdString(member.avatar_url));
+ QFuture<std::string> future = QtConcurrent::run([url] {
+ WebService::Client client(
+ QStringLiteral("%1://%2").arg(url.scheme(), url.host()).toStdString(), "", "");
+ auto result = client.GetImage(url.path().toStdString(), true);
+ if (result.returned_data.empty()) {
+ LOG_ERROR(WebService, "Failed to get avatar");
+ }
+ return result.returned_data;
+ });
+ auto* future_watcher = new QFutureWatcher<std::string>(this);
+ connect(future_watcher, &QFutureWatcher<std::string>::finished, this,
+ [this, future_watcher, avatar_url = member.avatar_url] {
+ const std::string result = future_watcher->result();
+ if (result.empty())
+ return;
+ QPixmap pixmap;
+ if (!pixmap.loadFromData(reinterpret_cast<const u8*>(result.data()),
+ static_cast<uint>(result.size())))
+ return;
+ icon_cache[avatar_url] =
+ pixmap.scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+ // Update all the displayed icons with the new icon_cache
+ UpdateIconDisplay();
+ });
+ future_watcher->setFuture(future);
+ }
+#endif
+
+ player_list->invisibleRootItem()->appendRow(name_item);
+ }
+ UpdateIconDisplay();
+ // TODO(B3N30): Restore row selection
+}
+
+void ChatRoom::OnChatTextChanged() {
+ if (ui->chat_message->text().length() > static_cast<int>(Network::MaxMessageSize))
+ ui->chat_message->setText(
+ ui->chat_message->text().left(static_cast<int>(Network::MaxMessageSize)));
+}
+
+void ChatRoom::PopupContextMenu(const QPoint& menu_location) {
+ QModelIndex item = ui->player_view->indexAt(menu_location);
+ if (!item.isValid())
+ return;
+
+ std::string nickname =
+ player_list->item(item.row())->data(PlayerListItem::NicknameRole).toString().toStdString();
+
+ QMenu context_menu;
+
+ QString username = player_list->item(item.row())->data(PlayerListItem::UsernameRole).toString();
+ if (!username.isEmpty()) {
+ QAction* view_profile_action = context_menu.addAction(tr("View Profile"));
+ connect(view_profile_action, &QAction::triggered, [username] {
+ QDesktopServices::openUrl(
+ QUrl(QStringLiteral("https://community.citra-emu.org/u/%1").arg(username)));
+ });
+ }
+
+ std::string cur_nickname;
+ if (auto room = room_network->GetRoomMember().lock()) {
+ cur_nickname = room->GetNickname();
+ }
+
+ if (nickname != cur_nickname) { // You can't block yourself
+ QAction* block_action = context_menu.addAction(tr("Block Player"));
+
+ block_action->setCheckable(true);
+ block_action->setChecked(block_list.count(nickname) > 0);
+
+ connect(block_action, &QAction::triggered, [this, nickname] {
+ if (block_list.count(nickname)) {
+ block_list.erase(nickname);
+ } else {
+ QMessageBox::StandardButton result = QMessageBox::question(
+ this, tr("Block Player"),
+ tr("When you block a player, you will no longer receive chat messages from "
+ "them.<br><br>Are you sure you would like to block %1?")
+ .arg(QString::fromStdString(nickname)),
+ QMessageBox::Yes | QMessageBox::No);
+ if (result == QMessageBox::Yes)
+ block_list.emplace(nickname);
+ }
+ });
+ }
+
+ if (has_mod_perms && nickname != cur_nickname) { // You can't kick or ban yourself
+ context_menu.addSeparator();
+
+ QAction* kick_action = context_menu.addAction(tr("Kick"));
+ QAction* ban_action = context_menu.addAction(tr("Ban"));
+
+ connect(kick_action, &QAction::triggered, [this, nickname] {
+ QMessageBox::StandardButton result =
+ QMessageBox::question(this, tr("Kick Player"),
+ tr("Are you sure you would like to <b>kick</b> %1?")
+ .arg(QString::fromStdString(nickname)),
+ QMessageBox::Yes | QMessageBox::No);
+ if (result == QMessageBox::Yes)
+ SendModerationRequest(Network::IdModKick, nickname);
+ });
+ connect(ban_action, &QAction::triggered, [this, nickname] {
+ QMessageBox::StandardButton result = QMessageBox::question(
+ this, tr("Ban Player"),
+ tr("Are you sure you would like to <b>kick and ban</b> %1?\n\nThis would "
+ "ban both their forum username and their IP address.")
+ .arg(QString::fromStdString(nickname)),
+ QMessageBox::Yes | QMessageBox::No);
+ if (result == QMessageBox::Yes)
+ SendModerationRequest(Network::IdModBan, nickname);
+ });
+ }
+
+ context_menu.exec(ui->player_view->viewport()->mapToGlobal(menu_location));
+}
diff --git a/src/yuzu/multiplayer/chat_room.h b/src/yuzu/multiplayer/chat_room.h
new file mode 100644
index 000000000..01c70fad0
--- /dev/null
+++ b/src/yuzu/multiplayer/chat_room.h
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <unordered_set>
+#include <QDialog>
+#include <QSortFilterProxyModel>
+#include <QStandardItemModel>
+#include <QVariant>
+#include "network/network.h"
+
+namespace Ui {
+class ChatRoom;
+}
+
+namespace Core {
+class AnnounceMultiplayerSession;
+}
+
+class ConnectionError;
+class ComboBoxProxyModel;
+
+class ChatMessage;
+
+class ChatRoom : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ChatRoom(QWidget* parent);
+ void Initialize(Network::RoomNetwork* room_network);
+ void RetranslateUi();
+ void SetPlayerList(const Network::RoomMember::MemberList& member_list);
+ void Clear();
+ void AppendStatusMessage(const QString& msg);
+ ~ChatRoom();
+
+ void SetModPerms(bool is_mod);
+ void UpdateIconDisplay();
+
+public slots:
+ void OnRoomUpdate(const Network::RoomInformation& info);
+ void OnChatReceive(const Network::ChatEntry&);
+ void OnStatusMessageReceive(const Network::StatusMessageEntry&);
+ void OnSendChat();
+ void OnChatTextChanged();
+ void PopupContextMenu(const QPoint& menu_location);
+ void Disable();
+ void Enable();
+
+signals:
+ void ChatReceived(const Network::ChatEntry&);
+ void StatusMessageReceived(const Network::StatusMessageEntry&);
+ void UserPinged();
+
+private:
+ static constexpr u32 max_chat_lines = 1000;
+ void AppendChatMessage(const QString&);
+ bool ValidateMessage(const std::string&);
+ void SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname);
+
+ bool has_mod_perms = false;
+ QStandardItemModel* player_list;
+ std::unique_ptr<Ui::ChatRoom> ui;
+ std::unordered_set<std::string> block_list;
+ std::unordered_map<std::string, QPixmap> icon_cache;
+ Network::RoomNetwork* room_network;
+};
+
+Q_DECLARE_METATYPE(Network::ChatEntry);
+Q_DECLARE_METATYPE(Network::StatusMessageEntry);
+Q_DECLARE_METATYPE(Network::RoomInformation);
+Q_DECLARE_METATYPE(Network::RoomMember::State);
+Q_DECLARE_METATYPE(Network::RoomMember::Error);
diff --git a/src/yuzu/multiplayer/chat_room.ui b/src/yuzu/multiplayer/chat_room.ui
new file mode 100644
index 000000000..f2b31b5da
--- /dev/null
+++ b/src/yuzu/multiplayer/chat_room.ui
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ChatRoom</class>
+ <widget class="QWidget" name="ChatRoom">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>807</width>
+ <height>432</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Room Window</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QTreeView" name="player_view"/>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QTextEdit" name="chat_history">
+ <property name="undoRedoEnabled">
+ <bool>false</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLineEdit" name="chat_message">
+ <property name="placeholderText">
+ <string>Send Chat Message</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="send_message">
+ <property name="text">
+ <string>Send Message</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp
new file mode 100644
index 000000000..caf34a414
--- /dev/null
+++ b/src/yuzu/multiplayer/client_room.cpp
@@ -0,0 +1,115 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <future>
+#include <QColor>
+#include <QImage>
+#include <QList>
+#include <QLocale>
+#include <QMetaType>
+#include <QTime>
+#include <QtConcurrent/QtConcurrentRun>
+#include "common/logging/log.h"
+#include "network/announce_multiplayer_session.h"
+#include "ui_client_room.h"
+#include "yuzu/game_list_p.h"
+#include "yuzu/multiplayer/client_room.h"
+#include "yuzu/multiplayer/message.h"
+#include "yuzu/multiplayer/moderation_dialog.h"
+#include "yuzu/multiplayer/state.h"
+
+ClientRoomWindow::ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_network_)
+ : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
+ ui(std::make_unique<Ui::ClientRoom>()), room_network{room_network_} {
+ ui->setupUi(this);
+ ui->chat->Initialize(&room_network);
+
+ // setup the callbacks for network updates
+ if (auto member = room_network.GetRoomMember().lock()) {
+ member->BindOnRoomInformationChanged(
+ [this](const Network::RoomInformation& info) { emit RoomInformationChanged(info); });
+ member->BindOnStateChanged(
+ [this](const Network::RoomMember::State& state) { emit StateChanged(state); });
+
+ connect(this, &ClientRoomWindow::RoomInformationChanged, this,
+ &ClientRoomWindow::OnRoomUpdate);
+ connect(this, &ClientRoomWindow::StateChanged, this, &::ClientRoomWindow::OnStateChange);
+ // Update the state
+ OnStateChange(member->GetState());
+ } else {
+ // TODO (jroweboy) network was not initialized?
+ }
+
+ connect(ui->disconnect, &QPushButton::clicked, this, &ClientRoomWindow::Disconnect);
+ ui->disconnect->setDefault(false);
+ ui->disconnect->setAutoDefault(false);
+ connect(ui->moderation, &QPushButton::clicked, [this] {
+ ModerationDialog dialog(room_network, this);
+ dialog.exec();
+ });
+ ui->moderation->setDefault(false);
+ ui->moderation->setAutoDefault(false);
+ connect(ui->chat, &ChatRoom::UserPinged, this, &ClientRoomWindow::ShowNotification);
+ UpdateView();
+}
+
+ClientRoomWindow::~ClientRoomWindow() = default;
+
+void ClientRoomWindow::SetModPerms(bool is_mod) {
+ ui->chat->SetModPerms(is_mod);
+ ui->moderation->setVisible(is_mod);
+ ui->moderation->setDefault(false);
+ ui->moderation->setAutoDefault(false);
+}
+
+void ClientRoomWindow::RetranslateUi() {
+ ui->retranslateUi(this);
+ ui->chat->RetranslateUi();
+}
+
+void ClientRoomWindow::OnRoomUpdate(const Network::RoomInformation& info) {
+ UpdateView();
+}
+
+void ClientRoomWindow::OnStateChange(const Network::RoomMember::State& state) {
+ if (state == Network::RoomMember::State::Joined ||
+ state == Network::RoomMember::State::Moderator) {
+ ui->chat->Clear();
+ ui->chat->AppendStatusMessage(tr("Connected"));
+ SetModPerms(state == Network::RoomMember::State::Moderator);
+ }
+ UpdateView();
+}
+
+void ClientRoomWindow::Disconnect() {
+ auto parent = static_cast<MultiplayerState*>(parentWidget());
+ if (parent->OnCloseRoom()) {
+ ui->chat->AppendStatusMessage(tr("Disconnected"));
+ close();
+ }
+}
+
+void ClientRoomWindow::UpdateView() {
+ if (auto member = room_network.GetRoomMember().lock()) {
+ if (member->IsConnected()) {
+ ui->chat->Enable();
+ ui->disconnect->setEnabled(true);
+ auto memberlist = member->GetMemberInformation();
+ ui->chat->SetPlayerList(memberlist);
+ const auto information = member->GetRoomInformation();
+ setWindowTitle(QString(tr("%1 - %2 (%3/%4 members) - connected"))
+ .arg(QString::fromStdString(information.name))
+ .arg(QString::fromStdString(information.preferred_game.name))
+ .arg(memberlist.size())
+ .arg(information.member_slots));
+ ui->description->setText(QString::fromStdString(information.description));
+ return;
+ }
+ }
+ // TODO(B3N30): can't get RoomMember*, show error and close window
+ close();
+}
+
+void ClientRoomWindow::UpdateIconDisplay() {
+ ui->chat->UpdateIconDisplay();
+}
diff --git a/src/yuzu/multiplayer/client_room.h b/src/yuzu/multiplayer/client_room.h
new file mode 100644
index 000000000..f338e3c59
--- /dev/null
+++ b/src/yuzu/multiplayer/client_room.h
@@ -0,0 +1,39 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "yuzu/multiplayer/chat_room.h"
+
+namespace Ui {
+class ClientRoom;
+}
+
+class ClientRoomWindow : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_network_);
+ ~ClientRoomWindow();
+
+ void RetranslateUi();
+ void UpdateIconDisplay();
+
+public slots:
+ void OnRoomUpdate(const Network::RoomInformation&);
+ void OnStateChange(const Network::RoomMember::State&);
+
+signals:
+ void RoomInformationChanged(const Network::RoomInformation&);
+ void StateChanged(const Network::RoomMember::State&);
+ void ShowNotification();
+
+private:
+ void Disconnect();
+ void UpdateView();
+ void SetModPerms(bool is_mod);
+
+ QStandardItemModel* player_list;
+ std::unique_ptr<Ui::ClientRoom> ui;
+ Network::RoomNetwork& room_network;
+};
diff --git a/src/yuzu/multiplayer/client_room.ui b/src/yuzu/multiplayer/client_room.ui
new file mode 100644
index 000000000..97e88b502
--- /dev/null
+++ b/src/yuzu/multiplayer/client_room.ui
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ClientRoom</class>
+ <widget class="QWidget" name="ClientRoom">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>807</width>
+ <height>432</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Room Window</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="description">
+ <property name="text">
+ <string>Room Description</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="moderation">
+ <property name="text">
+ <string>Moderation...</string>
+ </property>
+ <property name="visible">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="disconnect">
+ <property name="text">
+ <string>Leave Room</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="ChatRoom" name="chat" native="true"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ChatRoom</class>
+ <extends>QWidget</extends>
+ <header>multiplayer/chat_room.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp
new file mode 100644
index 000000000..10bf0a4fb
--- /dev/null
+++ b/src/yuzu/multiplayer/direct_connect.cpp
@@ -0,0 +1,143 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <QComboBox>
+#include <QFuture>
+#include <QIntValidator>
+#include <QRegExpValidator>
+#include <QString>
+#include <QtConcurrent/QtConcurrentRun>
+#include "common/settings.h"
+#include "core/core.h"
+#include "core/internal_network/network_interface.h"
+#include "network/network.h"
+#include "ui_direct_connect.h"
+#include "yuzu/main.h"
+#include "yuzu/multiplayer/client_room.h"
+#include "yuzu/multiplayer/direct_connect.h"
+#include "yuzu/multiplayer/message.h"
+#include "yuzu/multiplayer/state.h"
+#include "yuzu/multiplayer/validation.h"
+#include "yuzu/uisettings.h"
+
+enum class ConnectionType : u8 { TraversalServer, IP };
+
+DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent)
+ : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
+ ui(std::make_unique<Ui::DirectConnect>()), system{system_}, room_network{
+ system.GetRoomNetwork()} {
+
+ ui->setupUi(this);
+
+ // setup the watcher for background connections
+ watcher = new QFutureWatcher<void>;
+ connect(watcher, &QFutureWatcher<void>::finished, this, &DirectConnectWindow::OnConnection);
+
+ ui->nickname->setValidator(validation.GetNickname());
+ ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue());
+ if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
+ // Use yuzu Web Service user name as nickname by default
+ ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
+ }
+ ui->ip->setValidator(validation.GetIP());
+ ui->ip->setText(UISettings::values.multiplayer_ip.GetValue());
+ ui->port->setValidator(validation.GetPort());
+ ui->port->setText(QString::number(UISettings::values.multiplayer_port.GetValue()));
+
+ // TODO(jroweboy): Show or hide the connection options based on the current value of the combo
+ // box. Add this back in when the traversal server support is added.
+ connect(ui->connect, &QPushButton::clicked, this, &DirectConnectWindow::Connect);
+}
+
+DirectConnectWindow::~DirectConnectWindow() = default;
+
+void DirectConnectWindow::RetranslateUi() {
+ ui->retranslateUi(this);
+}
+
+void DirectConnectWindow::Connect() {
+ if (!Network::GetSelectedNetworkInterface()) {
+ NetworkMessage::ErrorManager::ShowError(
+ NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
+ return;
+ }
+ if (!ui->nickname->hasAcceptableInput()) {
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID);
+ return;
+ }
+ if (system.IsPoweredOn()) {
+ if (!NetworkMessage::WarnGameRunning()) {
+ return;
+ }
+ }
+ if (const auto member = room_network.GetRoomMember().lock()) {
+ // Prevent the user from trying to join a room while they are already joining.
+ if (member->GetState() == Network::RoomMember::State::Joining) {
+ return;
+ } else if (member->IsConnected()) {
+ // And ask if they want to leave the room if they are already in one.
+ if (!NetworkMessage::WarnDisconnect()) {
+ return;
+ }
+ }
+ }
+ switch (static_cast<ConnectionType>(ui->connection_type->currentIndex())) {
+ case ConnectionType::TraversalServer:
+ break;
+ case ConnectionType::IP:
+ if (!ui->ip->hasAcceptableInput()) {
+ NetworkMessage::ErrorManager::ShowError(
+ NetworkMessage::ErrorManager::IP_ADDRESS_NOT_VALID);
+ return;
+ }
+ if (!ui->port->hasAcceptableInput()) {
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID);
+ return;
+ }
+ break;
+ }
+
+ // Store settings
+ UISettings::values.multiplayer_nickname = ui->nickname->text();
+ UISettings::values.multiplayer_ip = ui->ip->text();
+ if (ui->port->isModified() && !ui->port->text().isEmpty()) {
+ UISettings::values.multiplayer_port = ui->port->text().toInt();
+ } else {
+ UISettings::values.multiplayer_port = UISettings::values.multiplayer_port.GetDefault();
+ }
+
+ emit SaveConfig();
+
+ // attempt to connect in a different thread
+ QFuture<void> f = QtConcurrent::run([&] {
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ auto port = UISettings::values.multiplayer_port.GetValue();
+ room_member->Join(ui->nickname->text().toStdString(),
+ ui->ip->text().toStdString().c_str(), port, 0, Network::NoPreferredIP,
+ ui->password->text().toStdString().c_str());
+ }
+ });
+ watcher->setFuture(f);
+ // and disable widgets and display a connecting while we wait
+ BeginConnecting();
+}
+
+void DirectConnectWindow::BeginConnecting() {
+ ui->connect->setEnabled(false);
+ ui->connect->setText(tr("Connecting"));
+}
+
+void DirectConnectWindow::EndConnecting() {
+ ui->connect->setEnabled(true);
+ ui->connect->setText(tr("Connect"));
+}
+
+void DirectConnectWindow::OnConnection() {
+ EndConnecting();
+
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ close();
+ }
+ }
+}
diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h
new file mode 100644
index 000000000..b8f66cfb2
--- /dev/null
+++ b/src/yuzu/multiplayer/direct_connect.h
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+#include <QFutureWatcher>
+#include "yuzu/multiplayer/validation.h"
+
+namespace Ui {
+class DirectConnect;
+}
+
+namespace Core {
+class System;
+}
+
+class DirectConnectWindow : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit DirectConnectWindow(Core::System& system_, QWidget* parent = nullptr);
+ ~DirectConnectWindow();
+
+ void RetranslateUi();
+
+signals:
+ /**
+ * Signalled by this widget when it is closing itself and destroying any state such as
+ * connections that it might have.
+ */
+ void Closed();
+ void SaveConfig();
+
+private slots:
+ void OnConnection();
+
+private:
+ void Connect();
+ void BeginConnecting();
+ void EndConnecting();
+
+ QFutureWatcher<void>* watcher;
+ std::unique_ptr<Ui::DirectConnect> ui;
+ Validation validation;
+ Core::System& system;
+ Network::RoomNetwork& room_network;
+};
diff --git a/src/yuzu/multiplayer/direct_connect.ui b/src/yuzu/multiplayer/direct_connect.ui
new file mode 100644
index 000000000..57d6ec25a
--- /dev/null
+++ b/src/yuzu/multiplayer/direct_connect.ui
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DirectConnect</class>
+ <widget class="QWidget" name="DirectConnect">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>455</width>
+ <height>161</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Direct Connect</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QComboBox" name="connection_type">
+ <item>
+ <property name="text">
+ <string>IP Address</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="ip_container" native="true">
+ <layout class="QHBoxLayout" name="ip_layout">
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>IP</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="ip">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;IPv4 address of the host&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="maxLength">
+ <number>16</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Port</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="port">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Port number the host is listening on&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="maxLength">
+ <number>5</number>
+ </property>
+ <property name="placeholderText">
+ <string notr="true" extracomment="placeholder string that tells user default port">24872</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Nickname</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="nickname">
+ <property name="maxLength">
+ <number>20</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Password</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="password"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="connect">
+ <property name="text">
+ <string>Connect</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp
new file mode 100644
index 000000000..a8faa5b24
--- /dev/null
+++ b/src/yuzu/multiplayer/host_room.cpp
@@ -0,0 +1,260 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <future>
+#include <QColor>
+#include <QImage>
+#include <QList>
+#include <QLocale>
+#include <QMessageBox>
+#include <QMetaType>
+#include <QTime>
+#include <QtConcurrent/QtConcurrentRun>
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "core/core.h"
+#include "core/internal_network/network_interface.h"
+#include "network/announce_multiplayer_session.h"
+#include "ui_host_room.h"
+#include "yuzu/game_list_p.h"
+#include "yuzu/main.h"
+#include "yuzu/multiplayer/host_room.h"
+#include "yuzu/multiplayer/message.h"
+#include "yuzu/multiplayer/state.h"
+#include "yuzu/multiplayer/validation.h"
+#include "yuzu/uisettings.h"
+#ifdef ENABLE_WEB_SERVICE
+#include "web_service/verify_user_jwt.h"
+#endif
+
+HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
+ std::shared_ptr<Core::AnnounceMultiplayerSession> session,
+ Core::System& system_)
+ : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
+ ui(std::make_unique<Ui::HostRoom>()),
+ announce_multiplayer_session(session), system{system_}, room_network{
+ system.GetRoomNetwork()} {
+ ui->setupUi(this);
+
+ // set up validation for all of the fields
+ ui->room_name->setValidator(validation.GetRoomName());
+ ui->username->setValidator(validation.GetNickname());
+ ui->port->setValidator(validation.GetPort());
+ ui->port->setPlaceholderText(QString::number(Network::DefaultRoomPort));
+
+ // Create a proxy to the game list to display the list of preferred games
+ game_list = new QStandardItemModel;
+ UpdateGameList(list);
+
+ proxy = new ComboBoxProxyModel;
+ proxy->setSourceModel(game_list);
+ proxy->sort(0, Qt::AscendingOrder);
+ ui->game_list->setModel(proxy);
+
+ // Connect all the widgets to the appropriate events
+ connect(ui->host, &QPushButton::clicked, this, &HostRoomWindow::Host);
+
+ // Restore the settings:
+ ui->username->setText(UISettings::values.multiplayer_room_nickname.GetValue());
+ if (ui->username->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
+ // Use yuzu Web Service user name as nickname by default
+ ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
+ }
+ ui->room_name->setText(UISettings::values.multiplayer_room_name.GetValue());
+ ui->port->setText(QString::number(UISettings::values.multiplayer_room_port.GetValue()));
+ ui->max_player->setValue(UISettings::values.multiplayer_max_player.GetValue());
+ int index = UISettings::values.multiplayer_host_type.GetValue();
+ if (index < ui->host_type->count()) {
+ ui->host_type->setCurrentIndex(index);
+ }
+ index = ui->game_list->findData(UISettings::values.multiplayer_game_id.GetValue(),
+ GameListItemPath::ProgramIdRole);
+ if (index != -1) {
+ ui->game_list->setCurrentIndex(index);
+ }
+ ui->room_description->setText(UISettings::values.multiplayer_room_description.GetValue());
+}
+
+HostRoomWindow::~HostRoomWindow() = default;
+
+void HostRoomWindow::UpdateGameList(QStandardItemModel* list) {
+ game_list->clear();
+ for (int i = 0; i < list->rowCount(); i++) {
+ auto parent = list->item(i, 0);
+ for (int j = 0; j < parent->rowCount(); j++) {
+ game_list->appendRow(parent->child(j)->clone());
+ }
+ }
+}
+
+void HostRoomWindow::RetranslateUi() {
+ ui->retranslateUi(this);
+}
+
+std::unique_ptr<Network::VerifyUser::Backend> HostRoomWindow::CreateVerifyBackend(
+ bool use_validation) const {
+ std::unique_ptr<Network::VerifyUser::Backend> verify_backend;
+ if (use_validation) {
+#ifdef ENABLE_WEB_SERVICE
+ verify_backend =
+ std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url.GetValue());
+#else
+ verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
+#endif
+ } else {
+ verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
+ }
+ return verify_backend;
+}
+
+void HostRoomWindow::Host() {
+ if (!Network::GetSelectedNetworkInterface()) {
+ NetworkMessage::ErrorManager::ShowError(
+ NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
+ return;
+ }
+ if (!ui->username->hasAcceptableInput()) {
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID);
+ return;
+ }
+ if (!ui->room_name->hasAcceptableInput()) {
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOMNAME_NOT_VALID);
+ return;
+ }
+ if (!ui->port->hasAcceptableInput()) {
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID);
+ return;
+ }
+ if (ui->game_list->currentIndex() == -1) {
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::GAME_NOT_SELECTED);
+ return;
+ }
+ if (system.IsPoweredOn()) {
+ if (!NetworkMessage::WarnGameRunning()) {
+ return;
+ }
+ }
+ if (auto member = room_network.GetRoomMember().lock()) {
+ if (member->GetState() == Network::RoomMember::State::Joining) {
+ return;
+ } else if (member->IsConnected()) {
+ auto parent = static_cast<MultiplayerState*>(parentWidget());
+ if (!parent->OnCloseRoom()) {
+ close();
+ return;
+ }
+ }
+ ui->host->setDisabled(true);
+
+ const AnnounceMultiplayerRoom::GameInfo game{
+ .name = ui->game_list->currentData(Qt::DisplayRole).toString().toStdString(),
+ .id = ui->game_list->currentData(GameListItemPath::ProgramIdRole).toULongLong(),
+ };
+ const auto port =
+ ui->port->isModified() ? ui->port->text().toInt() : Network::DefaultRoomPort;
+ const auto password = ui->password->text().toStdString();
+ const bool is_public = ui->host_type->currentIndex() == 0;
+ Network::Room::BanList ban_list{};
+ if (ui->load_ban_list->isChecked()) {
+ ban_list = UISettings::values.multiplayer_ban_list;
+ }
+ if (auto room = room_network.GetRoom().lock()) {
+ const bool created =
+ room->Create(ui->room_name->text().toStdString(),
+ ui->room_description->toPlainText().toStdString(), "", port, password,
+ ui->max_player->value(), Settings::values.yuzu_username.GetValue(),
+ game, CreateVerifyBackend(is_public), ban_list);
+ if (!created) {
+ NetworkMessage::ErrorManager::ShowError(
+ NetworkMessage::ErrorManager::COULD_NOT_CREATE_ROOM);
+ LOG_ERROR(Network, "Could not create room!");
+ ui->host->setEnabled(true);
+ return;
+ }
+ }
+ // Start the announce session if they chose Public
+ if (is_public) {
+ if (auto session = announce_multiplayer_session.lock()) {
+ // Register the room first to ensure verify_uid is present when we connect
+ WebService::WebResult result = session->Register();
+ if (result.result_code != WebService::WebResult::Code::Success) {
+ QMessageBox::warning(
+ this, tr("Error"),
+ tr("Failed to announce the room to the public lobby. In order to host a "
+ "room publicly, you must have a valid yuzu account configured in "
+ "Emulation -> Configure -> Web. If you do not want to publish a room in "
+ "the public lobby, then select Unlisted instead.\nDebug Message: ") +
+ QString::fromStdString(result.result_string),
+ QMessageBox::Ok);
+ ui->host->setEnabled(true);
+ if (auto room = room_network.GetRoom().lock()) {
+ room->Destroy();
+ }
+ return;
+ }
+ session->Start();
+ } else {
+ LOG_ERROR(Network, "Starting announce session failed");
+ }
+ }
+ std::string token;
+#ifdef ENABLE_WEB_SERVICE
+ if (is_public) {
+ WebService::Client client(Settings::values.web_api_url.GetValue(),
+ Settings::values.yuzu_username.GetValue(),
+ Settings::values.yuzu_token.GetValue());
+ if (auto room = room_network.GetRoom().lock()) {
+ token = client.GetExternalJWT(room->GetVerifyUID()).returned_data;
+ }
+ if (token.empty()) {
+ LOG_ERROR(WebService, "Could not get external JWT, verification may fail");
+ } else {
+ LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
+ }
+ }
+#endif
+ // TODO: Check what to do with this
+ member->Join(ui->username->text().toStdString(), "127.0.0.1", port, 0,
+ Network::NoPreferredIP, password, token);
+
+ // Store settings
+ UISettings::values.multiplayer_room_nickname = ui->username->text();
+ UISettings::values.multiplayer_room_name = ui->room_name->text();
+ UISettings::values.multiplayer_game_id =
+ ui->game_list->currentData(GameListItemPath::ProgramIdRole).toLongLong();
+ UISettings::values.multiplayer_max_player = ui->max_player->value();
+
+ UISettings::values.multiplayer_host_type = ui->host_type->currentIndex();
+ if (ui->port->isModified() && !ui->port->text().isEmpty()) {
+ UISettings::values.multiplayer_room_port = ui->port->text().toInt();
+ } else {
+ UISettings::values.multiplayer_room_port = Network::DefaultRoomPort;
+ }
+ UISettings::values.multiplayer_room_description = ui->room_description->toPlainText();
+ ui->host->setEnabled(true);
+ emit SaveConfig();
+ close();
+ }
+}
+
+QVariant ComboBoxProxyModel::data(const QModelIndex& idx, int role) const {
+ if (role != Qt::DisplayRole) {
+ auto val = QSortFilterProxyModel::data(idx, role);
+ // If its the icon, shrink it to 16x16
+ if (role == Qt::DecorationRole)
+ val = val.value<QImage>().scaled(16, 16, Qt::KeepAspectRatio);
+ return val;
+ }
+ std::string filename;
+ Common::SplitPath(
+ QSortFilterProxyModel::data(idx, GameListItemPath::FullPathRole).toString().toStdString(),
+ nullptr, &filename, nullptr);
+ QString title = QSortFilterProxyModel::data(idx, GameListItemPath::TitleRole).toString();
+ return title.isEmpty() ? QString::fromStdString(filename) : title;
+}
+
+bool ComboBoxProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
+ auto leftData = left.data(GameListItemPath::TitleRole).toString();
+ auto rightData = right.data(GameListItemPath::TitleRole).toString();
+ return leftData.compare(rightData) < 0;
+}
diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h
new file mode 100644
index 000000000..ae816e2e0
--- /dev/null
+++ b/src/yuzu/multiplayer/host_room.h
@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+#include <QSortFilterProxyModel>
+#include <QStandardItemModel>
+#include <QVariant>
+#include "network/network.h"
+#include "yuzu/multiplayer/chat_room.h"
+#include "yuzu/multiplayer/validation.h"
+
+namespace Ui {
+class HostRoom;
+}
+
+namespace Core {
+class System;
+class AnnounceMultiplayerSession;
+} // namespace Core
+
+class ConnectionError;
+class ComboBoxProxyModel;
+
+class ChatMessage;
+
+namespace Network::VerifyUser {
+class Backend;
+};
+
+class HostRoomWindow : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit HostRoomWindow(QWidget* parent, QStandardItemModel* list,
+ std::shared_ptr<Core::AnnounceMultiplayerSession> session,
+ Core::System& system_);
+ ~HostRoomWindow();
+
+ /**
+ * Updates the dialog with a new game list model.
+ * This model should be the original model of the game list.
+ */
+ void UpdateGameList(QStandardItemModel* list);
+ void RetranslateUi();
+
+signals:
+ void SaveConfig();
+
+private:
+ void Host();
+ std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) const;
+
+ std::unique_ptr<Ui::HostRoom> ui;
+ std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
+ QStandardItemModel* game_list;
+ ComboBoxProxyModel* proxy;
+ Validation validation;
+ Core::System& system;
+ Network::RoomNetwork& room_network;
+};
+
+/**
+ * Proxy Model for the game list combo box so we can reuse the game list model while still
+ * displaying the fields slightly differently
+ */
+class ComboBoxProxyModel : public QSortFilterProxyModel {
+ Q_OBJECT
+
+public:
+ int columnCount(const QModelIndex& idx) const override {
+ return 1;
+ }
+
+ QVariant data(const QModelIndex& idx, int role) const override;
+
+ bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
+};
diff --git a/src/yuzu/multiplayer/host_room.ui b/src/yuzu/multiplayer/host_room.ui
new file mode 100644
index 000000000..d54cf49c6
--- /dev/null
+++ b/src/yuzu/multiplayer/host_room.ui
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>HostRoom</class>
+ <widget class="QWidget" name="HostRoom">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>607</width>
+ <height>211</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Create Room</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QWidget" name="settings" native="true">
+ <layout class="QHBoxLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QFormLayout" name="formLayout_2">
+ <property name="labelAlignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Room Name</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="room_name">
+ <property name="maxLength">
+ <number>50</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Preferred Game</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="game_list"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Max Players</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSpinBox" name="max_player">
+ <property name="minimum">
+ <number>2</number>
+ </property>
+ <property name="maximum">
+ <number>16</number>
+ </property>
+ <property name="value">
+ <number>8</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <property name="labelAlignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="username"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Username</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="password">
+ <property name="echoMode">
+ <enum>QLineEdit::PasswordEchoOnEdit</enum>
+ </property>
+ <property name="placeholderText">
+ <string>(Leave blank for open game)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="port">
+ <property name="inputMethodHints">
+ <set>Qt::ImhDigitsOnly</set>
+ </property>
+ <property name="maxLength">
+ <number>5</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Password</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Port</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>Room Description</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextEdit" name="room_description"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QCheckBox" name="load_ban_list">
+ <property name="text">
+ <string>Load Previous Ban List</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QComboBox" name="host_type">
+ <item>
+ <property name="text">
+ <string>Public</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Unlisted</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="host">
+ <property name="text">
+ <string>Host Room</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp
new file mode 100644
index 000000000..08c275696
--- /dev/null
+++ b/src/yuzu/multiplayer/lobby.cpp
@@ -0,0 +1,410 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <QInputDialog>
+#include <QList>
+#include <QtConcurrent/QtConcurrentRun>
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "core/core.h"
+#include "core/hle/service/acc/profile_manager.h"
+#include "core/internal_network/network_interface.h"
+#include "network/network.h"
+#include "ui_lobby.h"
+#include "yuzu/game_list_p.h"
+#include "yuzu/main.h"
+#include "yuzu/multiplayer/client_room.h"
+#include "yuzu/multiplayer/lobby.h"
+#include "yuzu/multiplayer/lobby_p.h"
+#include "yuzu/multiplayer/message.h"
+#include "yuzu/multiplayer/state.h"
+#include "yuzu/multiplayer/validation.h"
+#include "yuzu/uisettings.h"
+#ifdef ENABLE_WEB_SERVICE
+#include "web_service/web_backend.h"
+#endif
+
+Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
+ std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_)
+ : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
+ ui(std::make_unique<Ui::Lobby>()), announce_multiplayer_session(session),
+ profile_manager(std::make_unique<Service::Account::ProfileManager>()), system{system_},
+ room_network{system.GetRoomNetwork()} {
+ ui->setupUi(this);
+
+ // setup the watcher for background connections
+ watcher = new QFutureWatcher<void>;
+
+ model = new QStandardItemModel(ui->room_list);
+
+ // Create a proxy to the game list to get the list of games owned
+ game_list = new QStandardItemModel;
+ UpdateGameList(list);
+
+ proxy = new LobbyFilterProxyModel(this, game_list);
+ proxy->setSourceModel(model);
+ proxy->setDynamicSortFilter(true);
+ proxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
+ proxy->setSortLocaleAware(true);
+ ui->room_list->setModel(proxy);
+ ui->room_list->header()->setSectionResizeMode(QHeaderView::Interactive);
+ ui->room_list->header()->stretchLastSection();
+ ui->room_list->setAlternatingRowColors(true);
+ ui->room_list->setSelectionMode(QHeaderView::SingleSelection);
+ ui->room_list->setSelectionBehavior(QHeaderView::SelectRows);
+ ui->room_list->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
+ ui->room_list->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
+ ui->room_list->setSortingEnabled(true);
+ ui->room_list->setEditTriggers(QHeaderView::NoEditTriggers);
+ ui->room_list->setExpandsOnDoubleClick(false);
+ ui->room_list->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ ui->nickname->setValidator(validation.GetNickname());
+ ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue());
+
+ // Try find the best nickname by default
+ if (ui->nickname->text().isEmpty() || ui->nickname->text() == QStringLiteral("yuzu")) {
+ if (!Settings::values.yuzu_username.GetValue().empty()) {
+ ui->nickname->setText(
+ QString::fromStdString(Settings::values.yuzu_username.GetValue()));
+ } else if (!GetProfileUsername().empty()) {
+ ui->nickname->setText(QString::fromStdString(GetProfileUsername()));
+ } else {
+ ui->nickname->setText(QStringLiteral("yuzu"));
+ }
+ }
+
+ // UI Buttons
+ connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby);
+ connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned);
+ connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull);
+ connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
+ connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom);
+ connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom);
+
+ // Actions
+ connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this,
+ &Lobby::OnRefreshLobby);
+}
+
+Lobby::~Lobby() = default;
+
+void Lobby::UpdateGameList(QStandardItemModel* list) {
+ game_list->clear();
+ for (int i = 0; i < list->rowCount(); i++) {
+ auto parent = list->item(i, 0);
+ for (int j = 0; j < parent->rowCount(); j++) {
+ game_list->appendRow(parent->child(j)->clone());
+ }
+ }
+ if (proxy)
+ proxy->UpdateGameList(game_list);
+ ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder);
+}
+
+void Lobby::RetranslateUi() {
+ ui->retranslateUi(this);
+}
+
+QString Lobby::PasswordPrompt() {
+ bool ok;
+ const QString text =
+ QInputDialog::getText(this, tr("Password Required to Join"), tr("Password:"),
+ QLineEdit::Password, QString(), &ok);
+ return ok ? text : QString();
+}
+
+void Lobby::OnExpandRoom(const QModelIndex& index) {
+ QModelIndex member_index = proxy->index(index.row(), Column::MEMBER);
+ auto member_list = proxy->data(member_index, LobbyItemMemberList::MemberListRole).toList();
+}
+
+void Lobby::OnJoinRoom(const QModelIndex& source) {
+ if (!Network::GetSelectedNetworkInterface()) {
+ LOG_INFO(WebService, "Automatically selected network interface for room network.");
+ Network::SelectFirstNetworkInterface();
+ }
+
+ if (!Network::GetSelectedNetworkInterface()) {
+ NetworkMessage::ErrorManager::ShowError(
+ NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
+ return;
+ }
+
+ if (system.IsPoweredOn()) {
+ if (!NetworkMessage::WarnGameRunning()) {
+ return;
+ }
+ }
+
+ if (const auto member = room_network.GetRoomMember().lock()) {
+ // Prevent the user from trying to join a room while they are already joining.
+ if (member->GetState() == Network::RoomMember::State::Joining) {
+ return;
+ } else if (member->IsConnected()) {
+ // And ask if they want to leave the room if they are already in one.
+ if (!NetworkMessage::WarnDisconnect()) {
+ return;
+ }
+ }
+ }
+ QModelIndex index = source;
+ // If the user double clicks on a child row (aka the player list) then use the parent instead
+ if (source.parent() != QModelIndex()) {
+ index = source.parent();
+ }
+ if (!ui->nickname->hasAcceptableInput()) {
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID);
+ return;
+ }
+
+ // Get a password to pass if the room is password protected
+ QModelIndex password_index = proxy->index(index.row(), Column::ROOM_NAME);
+ bool has_password = proxy->data(password_index, LobbyItemName::PasswordRole).toBool();
+ const std::string password = has_password ? PasswordPrompt().toStdString() : "";
+ if (has_password && password.empty()) {
+ return;
+ }
+
+ QModelIndex connection_index = proxy->index(index.row(), Column::HOST);
+ const std::string nickname = ui->nickname->text().toStdString();
+ const std::string ip =
+ proxy->data(connection_index, LobbyItemHost::HostIPRole).toString().toStdString();
+ int port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt();
+ const std::string verify_uid =
+ proxy->data(connection_index, LobbyItemHost::HostVerifyUIDRole).toString().toStdString();
+
+ // attempt to connect in a different thread
+ QFuture<void> f = QtConcurrent::run([nickname, ip, port, password, verify_uid, this] {
+ std::string token;
+#ifdef ENABLE_WEB_SERVICE
+ if (!Settings::values.yuzu_username.GetValue().empty() &&
+ !Settings::values.yuzu_token.GetValue().empty()) {
+ WebService::Client client(Settings::values.web_api_url.GetValue(),
+ Settings::values.yuzu_username.GetValue(),
+ Settings::values.yuzu_token.GetValue());
+ token = client.GetExternalJWT(verify_uid).returned_data;
+ if (token.empty()) {
+ LOG_ERROR(WebService, "Could not get external JWT, verification may fail");
+ } else {
+ LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
+ }
+ }
+#endif
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ room_member->Join(nickname, ip.c_str(), port, 0, Network::NoPreferredIP, password,
+ token);
+ }
+ });
+ watcher->setFuture(f);
+
+ // TODO(jroweboy): disable widgets and display a connecting while we wait
+
+ // Save settings
+ UISettings::values.multiplayer_nickname = ui->nickname->text();
+ UISettings::values.multiplayer_ip =
+ proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
+ UISettings::values.multiplayer_port =
+ proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt();
+ emit SaveConfig();
+}
+
+void Lobby::ResetModel() {
+ model->clear();
+ model->insertColumns(0, Column::TOTAL);
+ model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole);
+ model->setHeaderData(Column::ROOM_NAME, Qt::Horizontal, tr("Room Name"), Qt::DisplayRole);
+ model->setHeaderData(Column::GAME_NAME, Qt::Horizontal, tr("Preferred Game"), Qt::DisplayRole);
+ model->setHeaderData(Column::HOST, Qt::Horizontal, tr("Host"), Qt::DisplayRole);
+}
+
+void Lobby::RefreshLobby() {
+ if (auto session = announce_multiplayer_session.lock()) {
+ ResetModel();
+ ui->refresh_list->setEnabled(false);
+ ui->refresh_list->setText(tr("Refreshing"));
+ room_list_watcher.setFuture(
+ QtConcurrent::run([session]() { return session->GetRoomList(); }));
+ } else {
+ // TODO(jroweboy): Display an error box about announce couldn't be started
+ }
+}
+
+void Lobby::OnRefreshLobby() {
+ AnnounceMultiplayerRoom::RoomList new_room_list = room_list_watcher.result();
+ for (auto room : new_room_list) {
+ // find the icon for the game if this person owns that game.
+ QPixmap smdh_icon;
+ for (int r = 0; r < game_list->rowCount(); ++r) {
+ auto index = game_list->index(r, 0);
+ auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong();
+
+ if (game_id != 0 && room.information.preferred_game.id == game_id) {
+ smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>();
+ }
+ }
+
+ QList<QVariant> members;
+ for (auto member : room.members) {
+ QVariant var;
+ var.setValue(LobbyMember{QString::fromStdString(member.username),
+ QString::fromStdString(member.nickname), member.game.id,
+ QString::fromStdString(member.game.name)});
+ members.append(var);
+ }
+
+ auto first_item = new LobbyItemGame(
+ room.information.preferred_game.id,
+ QString::fromStdString(room.information.preferred_game.name), smdh_icon);
+ auto row = QList<QStandardItem*>({
+ first_item,
+ new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)),
+ new LobbyItemMemberList(members, room.information.member_slots),
+ new LobbyItemHost(QString::fromStdString(room.information.host_username),
+ QString::fromStdString(room.ip), room.information.port,
+ QString::fromStdString(room.verify_uid)),
+ });
+ model->appendRow(row);
+ // To make the rows expandable, add the member data as a child of the first column of the
+ // rows with people in them and have qt set them to colspan after the model is finished
+ // resetting
+ if (!room.information.description.empty()) {
+ first_item->appendRow(
+ new LobbyItemDescription(QString::fromStdString(room.information.description)));
+ }
+ if (!room.members.empty()) {
+ first_item->appendRow(new LobbyItemExpandedMemberList(members));
+ }
+ }
+
+ // Reenable the refresh button and resize the columns
+ ui->refresh_list->setEnabled(true);
+ ui->refresh_list->setText(tr("Refresh List"));
+ ui->room_list->header()->stretchLastSection();
+ for (int i = 0; i < Column::TOTAL - 1; ++i) {
+ ui->room_list->resizeColumnToContents(i);
+ }
+
+ // Set the member list child items to span all columns
+ for (int i = 0; i < proxy->rowCount(); i++) {
+ auto parent = model->item(i, 0);
+ for (int j = 0; j < parent->rowCount(); j++) {
+ ui->room_list->setFirstColumnSpanned(j, proxy->index(i, 0), true);
+ }
+ }
+
+ ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder);
+}
+
+std::string Lobby::GetProfileUsername() {
+ const auto& current_user = profile_manager->GetUser(Settings::values.current_user.GetValue());
+ Service::Account::ProfileBase profile{};
+
+ if (!current_user.has_value()) {
+ return "";
+ }
+
+ if (!profile_manager->GetProfileBase(*current_user, profile)) {
+ return "";
+ }
+
+ const auto text = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
+
+ return text;
+}
+
+LobbyFilterProxyModel::LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list)
+ : QSortFilterProxyModel(parent), game_list(list) {}
+
+void LobbyFilterProxyModel::UpdateGameList(QStandardItemModel* list) {
+ game_list = list;
+}
+
+bool LobbyFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const {
+ // Prioritize filters by fastest to compute
+
+ // pass over any child rows (aka row that shows the players in the room)
+ if (sourceParent != QModelIndex()) {
+ return true;
+ }
+
+ // filter by filled rooms
+ if (filter_full) {
+ QModelIndex member_list = sourceModel()->index(sourceRow, Column::MEMBER, sourceParent);
+ int player_count =
+ sourceModel()->data(member_list, LobbyItemMemberList::MemberListRole).toList().size();
+ int max_players =
+ sourceModel()->data(member_list, LobbyItemMemberList::MaxPlayerRole).toInt();
+ if (player_count >= max_players) {
+ return false;
+ }
+ }
+
+ // filter by search parameters
+ if (!filter_search.isEmpty()) {
+ QModelIndex game_name = sourceModel()->index(sourceRow, Column::GAME_NAME, sourceParent);
+ QModelIndex room_name = sourceModel()->index(sourceRow, Column::ROOM_NAME, sourceParent);
+ QModelIndex host_name = sourceModel()->index(sourceRow, Column::HOST, sourceParent);
+ bool preferred_game_match = sourceModel()
+ ->data(game_name, LobbyItemGame::GameNameRole)
+ .toString()
+ .contains(filter_search, filterCaseSensitivity());
+ bool room_name_match = sourceModel()
+ ->data(room_name, LobbyItemName::NameRole)
+ .toString()
+ .contains(filter_search, filterCaseSensitivity());
+ bool username_match = sourceModel()
+ ->data(host_name, LobbyItemHost::HostUsernameRole)
+ .toString()
+ .contains(filter_search, filterCaseSensitivity());
+ if (!preferred_game_match && !room_name_match && !username_match) {
+ return false;
+ }
+ }
+
+ // filter by game owned
+ if (filter_owned) {
+ QModelIndex game_name = sourceModel()->index(sourceRow, Column::GAME_NAME, sourceParent);
+ QList<QModelIndex> owned_games;
+ for (int r = 0; r < game_list->rowCount(); ++r) {
+ owned_games.append(QModelIndex(game_list->index(r, 0)));
+ }
+ auto current_id = sourceModel()->data(game_name, LobbyItemGame::TitleIDRole).toLongLong();
+ if (current_id == 0) {
+ // TODO(jroweboy): homebrew often doesn't have a game id and this hides them
+ return false;
+ }
+ bool owned = false;
+ for (const auto& game : owned_games) {
+ auto game_id = game_list->data(game, GameListItemPath::ProgramIdRole).toLongLong();
+ if (current_id == game_id) {
+ owned = true;
+ }
+ }
+ if (!owned) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void LobbyFilterProxyModel::sort(int column, Qt::SortOrder order) {
+ sourceModel()->sort(column, order);
+}
+
+void LobbyFilterProxyModel::SetFilterOwned(bool filter) {
+ filter_owned = filter;
+ invalidate();
+}
+
+void LobbyFilterProxyModel::SetFilterFull(bool filter) {
+ filter_full = filter;
+ invalidate();
+}
+
+void LobbyFilterProxyModel::SetFilterSearch(const QString& filter) {
+ filter_search = filter;
+ invalidate();
+}
diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h
new file mode 100644
index 000000000..300dad13e
--- /dev/null
+++ b/src/yuzu/multiplayer/lobby.h
@@ -0,0 +1,141 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+#include <QFutureWatcher>
+#include <QSortFilterProxyModel>
+#include <QStandardItemModel>
+#include "common/announce_multiplayer_room.h"
+#include "network/announce_multiplayer_session.h"
+#include "network/network.h"
+#include "yuzu/multiplayer/validation.h"
+
+namespace Ui {
+class Lobby;
+}
+
+class LobbyModel;
+class LobbyFilterProxyModel;
+
+namespace Core {
+class System;
+}
+
+namespace Service::Account {
+class ProfileManager;
+}
+
+/**
+ * Listing of all public games pulled from services. The lobby should be simple enough for users to
+ * find the game they want to play, and join it.
+ */
+class Lobby : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit Lobby(QWidget* parent, QStandardItemModel* list,
+ std::shared_ptr<Core::AnnounceMultiplayerSession> session,
+ Core::System& system_);
+ ~Lobby() override;
+
+ /**
+ * Updates the lobby with a new game list model.
+ * This model should be the original model of the game list.
+ */
+ void UpdateGameList(QStandardItemModel* list);
+ void RetranslateUi();
+
+public slots:
+ /**
+ * Begin the process to pull the latest room list from web services. After the listing is
+ * returned from web services, `LobbyRefreshed` will be signalled
+ */
+ void RefreshLobby();
+
+private slots:
+ /**
+ * Pulls the list of rooms from network and fills out the lobby model with the results
+ */
+ void OnRefreshLobby();
+
+ /**
+ * Handler for single clicking on a room in the list. Expands the treeitem to show player
+ * information for the people in the room
+ *
+ * index - The row of the proxy model that the user wants to join.
+ */
+ void OnExpandRoom(const QModelIndex&);
+
+ /**
+ * Handler for double clicking on a room in the list. Gathers the host ip and port and attempts
+ * to connect. Will also prompt for a password in case one is required.
+ *
+ * index - The row of the proxy model that the user wants to join.
+ */
+ void OnJoinRoom(const QModelIndex&);
+
+signals:
+ void StateChanged(const Network::RoomMember::State&);
+ void SaveConfig();
+
+private:
+ std::string GetProfileUsername();
+
+ /**
+ * Removes all entries in the Lobby before refreshing.
+ */
+ void ResetModel();
+
+ /**
+ * Prompts for a password. Returns an empty QString if the user either did not provide a
+ * password or if the user closed the window.
+ */
+ QString PasswordPrompt();
+
+ std::unique_ptr<Ui::Lobby> ui;
+
+ QStandardItemModel* model{};
+ QStandardItemModel* game_list{};
+ LobbyFilterProxyModel* proxy{};
+
+ QFutureWatcher<AnnounceMultiplayerRoom::RoomList> room_list_watcher;
+ std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
+ std::unique_ptr<Service::Account::ProfileManager> profile_manager;
+ QFutureWatcher<void>* watcher;
+ Validation validation;
+ Core::System& system;
+ Network::RoomNetwork& room_network;
+};
+
+/**
+ * Proxy Model for filtering the lobby
+ */
+class LobbyFilterProxyModel : public QSortFilterProxyModel {
+ Q_OBJECT;
+
+public:
+ explicit LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list);
+
+ /**
+ * Updates the filter with a new game list model.
+ * This model should be the processed one created by the Lobby.
+ */
+ void UpdateGameList(QStandardItemModel* list);
+
+ bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
+ void sort(int column, Qt::SortOrder order) override;
+
+public slots:
+ void SetFilterOwned(bool);
+ void SetFilterFull(bool);
+ void SetFilterSearch(const QString&);
+
+private:
+ QStandardItemModel* game_list;
+ bool filter_owned = false;
+ bool filter_full = false;
+ QString filter_search;
+};
diff --git a/src/yuzu/multiplayer/lobby.ui b/src/yuzu/multiplayer/lobby.ui
new file mode 100644
index 000000000..4c9901c9a
--- /dev/null
+++ b/src/yuzu/multiplayer/lobby.ui
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Lobby</class>
+ <widget class="QWidget" name="Lobby">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>903</width>
+ <height>487</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Public Room Browser</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Nickname</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="nickname">
+ <property name="placeholderText">
+ <string>Nickname</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Filters</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="search">
+ <property name="placeholderText">
+ <string>Search</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="games_owned">
+ <property name="text">
+ <string>Games I Own</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="hide_full">
+ <property name="text">
+ <string>Hide Full Rooms</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="refresh_list">
+ <property name="text">
+ <string>Refresh Lobby</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTreeView" name="room_list"/>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget" native="true"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/multiplayer/lobby_p.h b/src/yuzu/multiplayer/lobby_p.h
new file mode 100644
index 000000000..068c95aca
--- /dev/null
+++ b/src/yuzu/multiplayer/lobby_p.h
@@ -0,0 +1,244 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <utility>
+#include <QPixmap>
+#include <QStandardItem>
+#include <QStandardItemModel>
+#include "common/common_types.h"
+
+namespace Column {
+enum List {
+ GAME_NAME,
+ ROOM_NAME,
+ MEMBER,
+ HOST,
+ TOTAL,
+};
+}
+
+class LobbyItem : public QStandardItem {
+public:
+ LobbyItem() = default;
+ explicit LobbyItem(const QString& string) : QStandardItem(string) {}
+ virtual ~LobbyItem() override = default;
+};
+
+class LobbyItemName : public LobbyItem {
+public:
+ static const int NameRole = Qt::UserRole + 1;
+ static const int PasswordRole = Qt::UserRole + 2;
+
+ LobbyItemName() = default;
+ explicit LobbyItemName(bool has_password, QString name) : LobbyItem() {
+ setData(name, NameRole);
+ setData(has_password, PasswordRole);
+ }
+
+ QVariant data(int role) const override {
+ if (role == Qt::DecorationRole) {
+ bool has_password = data(PasswordRole).toBool();
+ return has_password ? QIcon::fromTheme(QStringLiteral("lock")).pixmap(16) : QIcon();
+ }
+ if (role != Qt::DisplayRole) {
+ return LobbyItem::data(role);
+ }
+ return data(NameRole).toString();
+ }
+
+ bool operator<(const QStandardItem& other) const override {
+ return data(NameRole).toString().localeAwareCompare(other.data(NameRole).toString()) < 0;
+ }
+};
+
+class LobbyItemDescription : public LobbyItem {
+public:
+ static const int DescriptionRole = Qt::UserRole + 1;
+
+ LobbyItemDescription() = default;
+ explicit LobbyItemDescription(QString description) {
+ setData(description, DescriptionRole);
+ }
+
+ QVariant data(int role) const override {
+ if (role != Qt::DisplayRole) {
+ return LobbyItem::data(role);
+ }
+ auto description = data(DescriptionRole).toString();
+ description.prepend(QStringLiteral("Description: "));
+ return description;
+ }
+
+ bool operator<(const QStandardItem& other) const override {
+ return data(DescriptionRole)
+ .toString()
+ .localeAwareCompare(other.data(DescriptionRole).toString()) < 0;
+ }
+};
+
+class LobbyItemGame : public LobbyItem {
+public:
+ static const int TitleIDRole = Qt::UserRole + 1;
+ static const int GameNameRole = Qt::UserRole + 2;
+ static const int GameIconRole = Qt::UserRole + 3;
+
+ LobbyItemGame() = default;
+ explicit LobbyItemGame(u64 title_id, QString game_name, QPixmap smdh_icon) {
+ setData(static_cast<unsigned long long>(title_id), TitleIDRole);
+ setData(game_name, GameNameRole);
+ if (!smdh_icon.isNull()) {
+ setData(smdh_icon, GameIconRole);
+ } else {
+ setData(QIcon::fromTheme(QStringLiteral("chip")).pixmap(32), GameIconRole);
+ }
+ }
+
+ QVariant data(int role) const override {
+ if (role == Qt::DecorationRole) {
+ auto val = data(GameIconRole);
+ if (val.isValid()) {
+ val = val.value<QPixmap>().scaled(32, 32, Qt::KeepAspectRatio,
+ Qt::TransformationMode::SmoothTransformation);
+ } else {
+ auto blank_image = QPixmap(32, 32);
+ blank_image.fill(Qt::black);
+ val = blank_image;
+ }
+ return val;
+ } else if (role != Qt::DisplayRole) {
+ return LobbyItem::data(role);
+ }
+ return data(GameNameRole).toString();
+ }
+
+ bool operator<(const QStandardItem& other) const override {
+ return data(GameNameRole)
+ .toString()
+ .localeAwareCompare(other.data(GameNameRole).toString()) < 0;
+ }
+};
+
+class LobbyItemHost : public LobbyItem {
+public:
+ static const int HostUsernameRole = Qt::UserRole + 1;
+ static const int HostIPRole = Qt::UserRole + 2;
+ static const int HostPortRole = Qt::UserRole + 3;
+ static const int HostVerifyUIDRole = Qt::UserRole + 4;
+
+ LobbyItemHost() = default;
+ explicit LobbyItemHost(QString username, QString ip, u16 port, QString verify_uid) {
+ setData(username, HostUsernameRole);
+ setData(ip, HostIPRole);
+ setData(port, HostPortRole);
+ setData(verify_uid, HostVerifyUIDRole);
+ }
+
+ QVariant data(int role) const override {
+ if (role != Qt::DisplayRole) {
+ return LobbyItem::data(role);
+ }
+ return data(HostUsernameRole).toString();
+ }
+
+ bool operator<(const QStandardItem& other) const override {
+ return data(HostUsernameRole)
+ .toString()
+ .localeAwareCompare(other.data(HostUsernameRole).toString()) < 0;
+ }
+};
+
+class LobbyMember {
+public:
+ LobbyMember() = default;
+ LobbyMember(const LobbyMember& other) = default;
+ explicit LobbyMember(QString username_, QString nickname_, u64 title_id_, QString game_name_)
+ : username(std::move(username_)), nickname(std::move(nickname_)), title_id(title_id_),
+ game_name(std::move(game_name_)) {}
+ ~LobbyMember() = default;
+
+ QString GetName() const {
+ if (username.isEmpty() || username == nickname) {
+ return nickname;
+ } else {
+ return QStringLiteral("%1 (%2)").arg(nickname, username);
+ }
+ }
+ u64 GetTitleId() const {
+ return title_id;
+ }
+ QString GetGameName() const {
+ return game_name;
+ }
+
+private:
+ QString username;
+ QString nickname;
+ u64 title_id;
+ QString game_name;
+};
+
+Q_DECLARE_METATYPE(LobbyMember);
+
+class LobbyItemMemberList : public LobbyItem {
+public:
+ static const int MemberListRole = Qt::UserRole + 1;
+ static const int MaxPlayerRole = Qt::UserRole + 2;
+
+ LobbyItemMemberList() = default;
+ explicit LobbyItemMemberList(QList<QVariant> members, u32 max_players) {
+ setData(members, MemberListRole);
+ setData(max_players, MaxPlayerRole);
+ }
+
+ QVariant data(int role) const override {
+ if (role != Qt::DisplayRole) {
+ return LobbyItem::data(role);
+ }
+ auto members = data(MemberListRole).toList();
+ return QStringLiteral("%1 / %2 ")
+ .arg(QString::number(members.size()), data(MaxPlayerRole).toString());
+ }
+
+ bool operator<(const QStandardItem& other) const override {
+ // sort by rooms that have the most players
+ int left_members = data(MemberListRole).toList().size();
+ int right_members = other.data(MemberListRole).toList().size();
+ return left_members < right_members;
+ }
+};
+
+/**
+ * Member information for when a lobby is expanded in the UI
+ */
+class LobbyItemExpandedMemberList : public LobbyItem {
+public:
+ static const int MemberListRole = Qt::UserRole + 1;
+
+ LobbyItemExpandedMemberList() = default;
+ explicit LobbyItemExpandedMemberList(QList<QVariant> members) {
+ setData(members, MemberListRole);
+ }
+
+ QVariant data(int role) const override {
+ if (role != Qt::DisplayRole) {
+ return LobbyItem::data(role);
+ }
+ auto members = data(MemberListRole).toList();
+ QString out;
+ bool first = true;
+ for (const auto& member : members) {
+ if (!first)
+ out.append(QStringLiteral("\n"));
+ const auto& m = member.value<LobbyMember>();
+ if (m.GetGameName().isEmpty()) {
+ out += QString(QObject::tr("%1 is not playing a game")).arg(m.GetName());
+ } else {
+ out += QString(QObject::tr("%1 is playing %2")).arg(m.GetName(), m.GetGameName());
+ }
+ first = false;
+ }
+ return out;
+ }
+};
diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp
new file mode 100644
index 000000000..6d8f18274
--- /dev/null
+++ b/src/yuzu/multiplayer/message.cpp
@@ -0,0 +1,85 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <QMessageBox>
+#include <QString>
+
+#include "yuzu/multiplayer/message.h"
+
+namespace NetworkMessage {
+const ConnectionError ErrorManager::USERNAME_NOT_VALID(
+ QT_TR_NOOP("Username is not valid. Must be 4 to 20 alphanumeric characters."));
+const ConnectionError ErrorManager::ROOMNAME_NOT_VALID(
+ QT_TR_NOOP("Room name is not valid. Must be 4 to 20 alphanumeric characters."));
+const ConnectionError ErrorManager::USERNAME_NOT_VALID_SERVER(
+ QT_TR_NOOP("Username is already in use or not valid. Please choose another."));
+const ConnectionError ErrorManager::IP_ADDRESS_NOT_VALID(
+ QT_TR_NOOP("IP is not a valid IPv4 address."));
+const ConnectionError ErrorManager::PORT_NOT_VALID(
+ QT_TR_NOOP("Port must be a number between 0 to 65535."));
+const ConnectionError ErrorManager::GAME_NOT_SELECTED(QT_TR_NOOP(
+ "You must choose a Preferred Game to host a room. If you do not have any games in your game "
+ "list yet, add a game folder by clicking on the plus icon in the game list."));
+const ConnectionError ErrorManager::NO_INTERNET(
+ QT_TR_NOOP("Unable to find an internet connection. Check your internet settings."));
+const ConnectionError ErrorManager::UNABLE_TO_CONNECT(
+ QT_TR_NOOP("Unable to connect to the host. Verify that the connection settings are correct. If "
+ "you still cannot connect, contact the room host and verify that the host is "
+ "properly configured with the external port forwarded."));
+const ConnectionError ErrorManager::ROOM_IS_FULL(
+ QT_TR_NOOP("Unable to connect to the room because it is already full."));
+const ConnectionError ErrorManager::COULD_NOT_CREATE_ROOM(
+ QT_TR_NOOP("Creating a room failed. Please retry. Restarting yuzu might be necessary."));
+const ConnectionError ErrorManager::HOST_BANNED(
+ QT_TR_NOOP("The host of the room has banned you. Speak with the host to unban you "
+ "or try a different room."));
+const ConnectionError ErrorManager::WRONG_VERSION(
+ QT_TR_NOOP("Version mismatch! Please update to the latest version of yuzu. If the problem "
+ "persists, contact the room host and ask them to update the server."));
+const ConnectionError ErrorManager::WRONG_PASSWORD(QT_TR_NOOP("Incorrect password."));
+const ConnectionError ErrorManager::GENERIC_ERROR(QT_TR_NOOP(
+ "An unknown error occurred. If this error continues to occur, please open an issue"));
+const ConnectionError ErrorManager::LOST_CONNECTION(
+ QT_TR_NOOP("Connection to room lost. Try to reconnect."));
+const ConnectionError ErrorManager::HOST_KICKED(
+ QT_TR_NOOP("You have been kicked by the room host."));
+const ConnectionError ErrorManager::IP_COLLISION(
+ QT_TR_NOOP("IP address is already in use. Please choose another."));
+const ConnectionError ErrorManager::PERMISSION_DENIED(
+ QT_TR_NOOP("You do not have enough permission to perform this action."));
+const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP(
+ "The user you are trying to kick/ban could not be found.\nThey may have left the room."));
+const ConnectionError ErrorManager::NO_INTERFACE_SELECTED(QT_TR_NOOP(
+ "No valid network interface is selected.\nPlease go to Configure -> System -> Network and "
+ "make a selection."));
+
+static bool WarnMessage(const std::string& title, const std::string& text) {
+ return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()),
+ QObject::tr(text.c_str()),
+ QMessageBox::Ok | QMessageBox::Cancel);
+}
+
+void ErrorManager::ShowError(const ConnectionError& e) {
+ QMessageBox::critical(nullptr, tr("Error"), tr(e.GetString().c_str()));
+}
+
+bool WarnGameRunning() {
+ return WarnMessage(
+ QT_TR_NOOP("Game already running"),
+ QT_TR_NOOP("Joining a room when the game is already running is discouraged "
+ "and can cause the room feature not to work correctly.\nProceed anyway?"));
+}
+
+bool WarnCloseRoom() {
+ return WarnMessage(
+ QT_TR_NOOP("Leave Room"),
+ QT_TR_NOOP("You are about to close the room. Any network connections will be closed."));
+}
+
+bool WarnDisconnect() {
+ return WarnMessage(
+ QT_TR_NOOP("Disconnect"),
+ QT_TR_NOOP("You are about to leave the room. Any network connections will be closed."));
+}
+
+} // namespace NetworkMessage
diff --git a/src/yuzu/multiplayer/message.h b/src/yuzu/multiplayer/message.h
new file mode 100644
index 000000000..f038b9a1f
--- /dev/null
+++ b/src/yuzu/multiplayer/message.h
@@ -0,0 +1,72 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <utility>
+
+namespace NetworkMessage {
+
+class ConnectionError {
+
+public:
+ explicit ConnectionError(std::string str) : err(std::move(str)) {}
+ const std::string& GetString() const {
+ return err;
+ }
+
+private:
+ std::string err;
+};
+
+class ErrorManager : QObject {
+ Q_OBJECT
+public:
+ /// When the nickname is considered invalid by the client
+ static const ConnectionError USERNAME_NOT_VALID;
+ static const ConnectionError ROOMNAME_NOT_VALID;
+ /// When the nickname is considered invalid by the room server
+ static const ConnectionError USERNAME_NOT_VALID_SERVER;
+ static const ConnectionError IP_ADDRESS_NOT_VALID;
+ static const ConnectionError PORT_NOT_VALID;
+ static const ConnectionError GAME_NOT_SELECTED;
+ static const ConnectionError NO_INTERNET;
+ static const ConnectionError UNABLE_TO_CONNECT;
+ static const ConnectionError ROOM_IS_FULL;
+ static const ConnectionError COULD_NOT_CREATE_ROOM;
+ static const ConnectionError HOST_BANNED;
+ static const ConnectionError WRONG_VERSION;
+ static const ConnectionError WRONG_PASSWORD;
+ static const ConnectionError GENERIC_ERROR;
+ static const ConnectionError LOST_CONNECTION;
+ static const ConnectionError HOST_KICKED;
+ static const ConnectionError IP_COLLISION;
+ static const ConnectionError PERMISSION_DENIED;
+ static const ConnectionError NO_SUCH_USER;
+ static const ConnectionError NO_INTERFACE_SELECTED;
+ /**
+ * Shows a standard QMessageBox with a error message
+ */
+ static void ShowError(const ConnectionError& e);
+};
+
+/**
+ * Show a standard QMessageBox with a warning message about joining a room when
+ * the game is already running
+ * return true if the user wants to close the network connection
+ */
+bool WarnGameRunning();
+
+/**
+ * Show a standard QMessageBox with a warning message about leaving the room
+ * return true if the user wants to close the network connection
+ */
+bool WarnCloseRoom();
+
+/**
+ * Show a standard QMessageBox with a warning message about disconnecting from the room
+ * return true if the user wants to disconnect
+ */
+bool WarnDisconnect();
+
+} // namespace NetworkMessage
diff --git a/src/yuzu/multiplayer/moderation_dialog.cpp b/src/yuzu/multiplayer/moderation_dialog.cpp
new file mode 100644
index 000000000..c9b8ed397
--- /dev/null
+++ b/src/yuzu/multiplayer/moderation_dialog.cpp
@@ -0,0 +1,112 @@
+// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <QStandardItem>
+#include <QStandardItemModel>
+#include "network/network.h"
+#include "network/room_member.h"
+#include "ui_moderation_dialog.h"
+#include "yuzu/multiplayer/moderation_dialog.h"
+
+namespace Column {
+enum {
+ SUBJECT,
+ TYPE,
+ COUNT,
+};
+}
+
+ModerationDialog::ModerationDialog(Network::RoomNetwork& room_network_, QWidget* parent)
+ : QDialog(parent), ui(std::make_unique<Ui::ModerationDialog>()), room_network{room_network_} {
+ ui->setupUi(this);
+
+ qRegisterMetaType<Network::Room::BanList>();
+
+ if (auto member = room_network.GetRoomMember().lock()) {
+ callback_handle_status_message = member->BindOnStatusMessageReceived(
+ [this](const Network::StatusMessageEntry& status_message) {
+ emit StatusMessageReceived(status_message);
+ });
+ connect(this, &ModerationDialog::StatusMessageReceived, this,
+ &ModerationDialog::OnStatusMessageReceived);
+ callback_handle_ban_list = member->BindOnBanListReceived(
+ [this](const Network::Room::BanList& ban_list) { emit BanListReceived(ban_list); });
+ connect(this, &ModerationDialog::BanListReceived, this, &ModerationDialog::PopulateBanList);
+ }
+
+ // Initialize the UI
+ model = new QStandardItemModel(ui->ban_list_view);
+ model->insertColumns(0, Column::COUNT);
+ model->setHeaderData(Column::SUBJECT, Qt::Horizontal, tr("Subject"));
+ model->setHeaderData(Column::TYPE, Qt::Horizontal, tr("Type"));
+
+ ui->ban_list_view->setModel(model);
+
+ // Load the ban list in background
+ LoadBanList();
+
+ connect(ui->refresh, &QPushButton::clicked, this, [this] { LoadBanList(); });
+ connect(ui->unban, &QPushButton::clicked, this, [this] {
+ auto index = ui->ban_list_view->currentIndex();
+ SendUnbanRequest(model->item(index.row(), 0)->text());
+ });
+ connect(ui->ban_list_view, &QTreeView::clicked, [this] { ui->unban->setEnabled(true); });
+}
+
+ModerationDialog::~ModerationDialog() {
+ if (callback_handle_status_message) {
+ if (auto room = room_network.GetRoomMember().lock()) {
+ room->Unbind(callback_handle_status_message);
+ }
+ }
+
+ if (callback_handle_ban_list) {
+ if (auto room = room_network.GetRoomMember().lock()) {
+ room->Unbind(callback_handle_ban_list);
+ }
+ }
+}
+
+void ModerationDialog::LoadBanList() {
+ if (auto room = room_network.GetRoomMember().lock()) {
+ ui->refresh->setEnabled(false);
+ ui->refresh->setText(tr("Refreshing"));
+ ui->unban->setEnabled(false);
+ room->RequestBanList();
+ }
+}
+
+void ModerationDialog::PopulateBanList(const Network::Room::BanList& ban_list) {
+ model->removeRows(0, model->rowCount());
+ for (const auto& username : ban_list.first) {
+ QStandardItem* subject_item = new QStandardItem(QString::fromStdString(username));
+ QStandardItem* type_item = new QStandardItem(tr("Forum Username"));
+ model->invisibleRootItem()->appendRow({subject_item, type_item});
+ }
+ for (const auto& ip : ban_list.second) {
+ QStandardItem* subject_item = new QStandardItem(QString::fromStdString(ip));
+ QStandardItem* type_item = new QStandardItem(tr("IP Address"));
+ model->invisibleRootItem()->appendRow({subject_item, type_item});
+ }
+ for (int i = 0; i < Column::COUNT - 1; ++i) {
+ ui->ban_list_view->resizeColumnToContents(i);
+ }
+ ui->refresh->setEnabled(true);
+ ui->refresh->setText(tr("Refresh"));
+ ui->unban->setEnabled(false);
+}
+
+void ModerationDialog::SendUnbanRequest(const QString& subject) {
+ if (auto room = room_network.GetRoomMember().lock()) {
+ room->SendModerationRequest(Network::IdModUnban, subject.toStdString());
+ }
+}
+
+void ModerationDialog::OnStatusMessageReceived(const Network::StatusMessageEntry& status_message) {
+ if (status_message.type != Network::IdMemberBanned &&
+ status_message.type != Network::IdAddressUnbanned)
+ return;
+
+ // Update the ban list for ban/unban
+ LoadBanList();
+}
diff --git a/src/yuzu/multiplayer/moderation_dialog.h b/src/yuzu/multiplayer/moderation_dialog.h
new file mode 100644
index 000000000..e9e5daff7
--- /dev/null
+++ b/src/yuzu/multiplayer/moderation_dialog.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <QDialog>
+#include "network/room.h"
+#include "network/room_member.h"
+
+namespace Ui {
+class ModerationDialog;
+}
+
+class QStandardItemModel;
+
+class ModerationDialog : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ModerationDialog(Network::RoomNetwork& room_network_, QWidget* parent = nullptr);
+ ~ModerationDialog();
+
+signals:
+ void StatusMessageReceived(const Network::StatusMessageEntry&);
+ void BanListReceived(const Network::Room::BanList&);
+
+private:
+ void LoadBanList();
+ void PopulateBanList(const Network::Room::BanList& ban_list);
+ void SendUnbanRequest(const QString& subject);
+ void OnStatusMessageReceived(const Network::StatusMessageEntry& status_message);
+
+ std::unique_ptr<Ui::ModerationDialog> ui;
+ QStandardItemModel* model;
+ Network::RoomMember::CallbackHandle<Network::StatusMessageEntry> callback_handle_status_message;
+ Network::RoomMember::CallbackHandle<Network::Room::BanList> callback_handle_ban_list;
+
+ Network::RoomNetwork& room_network;
+};
+
+Q_DECLARE_METATYPE(Network::Room::BanList);
diff --git a/src/yuzu/multiplayer/moderation_dialog.ui b/src/yuzu/multiplayer/moderation_dialog.ui
new file mode 100644
index 000000000..808d99414
--- /dev/null
+++ b/src/yuzu/multiplayer/moderation_dialog.ui
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ModerationDialog</class>
+ <widget class="QDialog" name="ModerationDialog">
+ <property name="windowTitle">
+ <string>Moderation</string>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>500</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QGroupBox" name="ban_list_group_box">
+ <property name="title">
+ <string>Ban List</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="refresh">
+ <property name="text">
+ <string>Refreshing</string>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="unban">
+ <property name="text">
+ <string>Unban</string>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTreeView" name="ban_list_view"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ModerationDialog</receiver>
+ <slot>accept()</slot>
+ </connection>
+ </connections>
+ <resources/>
+</ui>
diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp
new file mode 100644
index 000000000..ae2738ad4
--- /dev/null
+++ b/src/yuzu/multiplayer/state.cpp
@@ -0,0 +1,336 @@
+// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <QAction>
+#include <QApplication>
+#include <QIcon>
+#include <QMessageBox>
+#include <QStandardItemModel>
+#include "common/announce_multiplayer_room.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "yuzu/game_list.h"
+#include "yuzu/multiplayer/client_room.h"
+#include "yuzu/multiplayer/direct_connect.h"
+#include "yuzu/multiplayer/host_room.h"
+#include "yuzu/multiplayer/lobby.h"
+#include "yuzu/multiplayer/message.h"
+#include "yuzu/multiplayer/state.h"
+#include "yuzu/uisettings.h"
+#include "yuzu/util/clickable_label.h"
+
+MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_,
+ QAction* leave_room_, QAction* show_room_, Core::System& system_)
+ : QWidget(parent), game_list_model(game_list_model_), leave_room(leave_room_),
+ show_room(show_room_), system{system_}, room_network{system.GetRoomNetwork()} {
+ if (auto member = room_network.GetRoomMember().lock()) {
+ // register the network structs to use in slots and signals
+ state_callback_handle = member->BindOnStateChanged(
+ [this](const Network::RoomMember::State& state) { emit NetworkStateChanged(state); });
+ connect(this, &MultiplayerState::NetworkStateChanged, this,
+ &MultiplayerState::OnNetworkStateChanged);
+ error_callback_handle = member->BindOnError(
+ [this](const Network::RoomMember::Error& error) { emit NetworkError(error); });
+ connect(this, &MultiplayerState::NetworkError, this, &MultiplayerState::OnNetworkError);
+ }
+
+ qRegisterMetaType<Network::RoomMember::State>();
+ qRegisterMetaType<Network::RoomMember::Error>();
+ qRegisterMetaType<WebService::WebResult>();
+ announce_multiplayer_session = std::make_shared<Core::AnnounceMultiplayerSession>(room_network);
+ announce_multiplayer_session->BindErrorCallback(
+ [this](const WebService::WebResult& result) { emit AnnounceFailed(result); });
+ connect(this, &MultiplayerState::AnnounceFailed, this, &MultiplayerState::OnAnnounceFailed);
+
+ status_text = new ClickableLabel(this);
+ status_icon = new ClickableLabel(this);
+
+ connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
+ connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
+
+ connect(static_cast<QApplication*>(QApplication::instance()), &QApplication::focusChanged, this,
+ [this](QWidget* /*old*/, QWidget* now) {
+ if (client_room && client_room->isAncestorOf(now)) {
+ HideNotification();
+ }
+ });
+
+ retranslateUi();
+}
+
+MultiplayerState::~MultiplayerState() = default;
+
+void MultiplayerState::Close() {
+ if (state_callback_handle) {
+ if (auto member = room_network.GetRoomMember().lock()) {
+ member->Unbind(state_callback_handle);
+ }
+ }
+
+ if (error_callback_handle) {
+ if (auto member = room_network.GetRoomMember().lock()) {
+ member->Unbind(error_callback_handle);
+ }
+ }
+ if (host_room) {
+ host_room->close();
+ }
+ if (direct_connect) {
+ direct_connect->close();
+ }
+ if (client_room) {
+ client_room->close();
+ }
+ if (lobby) {
+ lobby->close();
+ }
+}
+
+void MultiplayerState::retranslateUi() {
+ status_text->setToolTip(tr("Current connection status"));
+
+ UpdateNotificationStatus();
+
+ if (lobby) {
+ lobby->RetranslateUi();
+ }
+ if (host_room) {
+ host_room->RetranslateUi();
+ }
+ if (client_room) {
+ client_room->RetranslateUi();
+ }
+ if (direct_connect) {
+ direct_connect->RetranslateUi();
+ }
+}
+
+void MultiplayerState::SetNotificationStatus(NotificationStatus status) {
+ notification_status = status;
+ UpdateNotificationStatus();
+}
+
+void MultiplayerState::UpdateNotificationStatus() {
+ switch (notification_status) {
+ case NotificationStatus::Unitialized:
+ status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
+ status_text->setText(tr("Not Connected. Click here to find a room!"));
+ leave_room->setEnabled(false);
+ show_room->setEnabled(false);
+ break;
+ case NotificationStatus::Disconnected:
+ status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
+ status_text->setText(tr("Not Connected"));
+ leave_room->setEnabled(false);
+ show_room->setEnabled(false);
+ break;
+ case NotificationStatus::Connected:
+ status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16));
+ status_text->setText(tr("Connected"));
+ leave_room->setEnabled(true);
+ show_room->setEnabled(true);
+ break;
+ case NotificationStatus::Notification:
+ status_icon->setPixmap(
+ QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16));
+ status_text->setText(tr("New Messages Received"));
+ leave_room->setEnabled(true);
+ show_room->setEnabled(true);
+ break;
+ }
+
+ // Clean up status bar if game is running
+ if (system.IsPoweredOn()) {
+ status_text->clear();
+ }
+}
+
+void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) {
+ LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state));
+ if (state == Network::RoomMember::State::Joined ||
+ state == Network::RoomMember::State::Moderator) {
+
+ OnOpenNetworkRoom();
+ SetNotificationStatus(NotificationStatus::Connected);
+ } else {
+ SetNotificationStatus(NotificationStatus::Disconnected);
+ }
+
+ current_state = state;
+}
+
+void MultiplayerState::OnNetworkError(const Network::RoomMember::Error& error) {
+ LOG_DEBUG(Frontend, "Network Error: {}", Network::GetErrorStr(error));
+ switch (error) {
+ case Network::RoomMember::Error::LostConnection:
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::LOST_CONNECTION);
+ break;
+ case Network::RoomMember::Error::HostKicked:
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::HOST_KICKED);
+ break;
+ case Network::RoomMember::Error::CouldNotConnect:
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::UNABLE_TO_CONNECT);
+ break;
+ case Network::RoomMember::Error::NameCollision:
+ NetworkMessage::ErrorManager::ShowError(
+ NetworkMessage::ErrorManager::USERNAME_NOT_VALID_SERVER);
+ break;
+ case Network::RoomMember::Error::IpCollision:
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::IP_COLLISION);
+ break;
+ case Network::RoomMember::Error::RoomIsFull:
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOM_IS_FULL);
+ break;
+ case Network::RoomMember::Error::WrongPassword:
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::WRONG_PASSWORD);
+ break;
+ case Network::RoomMember::Error::WrongVersion:
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::WRONG_VERSION);
+ break;
+ case Network::RoomMember::Error::HostBanned:
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::HOST_BANNED);
+ break;
+ case Network::RoomMember::Error::UnknownError:
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::UNABLE_TO_CONNECT);
+ break;
+ case Network::RoomMember::Error::PermissionDenied:
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PERMISSION_DENIED);
+ break;
+ case Network::RoomMember::Error::NoSuchUser:
+ NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::NO_SUCH_USER);
+ break;
+ }
+}
+
+void MultiplayerState::OnAnnounceFailed(const WebService::WebResult& result) {
+ announce_multiplayer_session->Stop();
+ QMessageBox::warning(this, tr("Error"),
+ tr("Failed to update the room information. Please check your Internet "
+ "connection and try hosting the room again.\nDebug Message: ") +
+ QString::fromStdString(result.result_string),
+ QMessageBox::Ok);
+}
+
+void MultiplayerState::OnSaveConfig() {
+ emit SaveConfig();
+}
+
+void MultiplayerState::UpdateThemedIcons() {
+ if (show_notification) {
+ status_icon->setPixmap(
+ QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16));
+ } else if (current_state == Network::RoomMember::State::Joined ||
+ current_state == Network::RoomMember::State::Moderator) {
+
+ status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16));
+ } else {
+ status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
+ }
+ if (client_room)
+ client_room->UpdateIconDisplay();
+}
+
+static void BringWidgetToFront(QWidget* widget) {
+ widget->show();
+ widget->activateWindow();
+ widget->raise();
+}
+
+void MultiplayerState::OnViewLobby() {
+ if (lobby == nullptr) {
+ lobby = new Lobby(this, game_list_model, announce_multiplayer_session, system);
+ connect(lobby, &Lobby::SaveConfig, this, &MultiplayerState::OnSaveConfig);
+ }
+ lobby->RefreshLobby();
+ BringWidgetToFront(lobby);
+}
+
+void MultiplayerState::OnCreateRoom() {
+ if (host_room == nullptr) {
+ host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session, system);
+ connect(host_room, &HostRoomWindow::SaveConfig, this, &MultiplayerState::OnSaveConfig);
+ }
+ BringWidgetToFront(host_room);
+}
+
+bool MultiplayerState::OnCloseRoom() {
+ if (!NetworkMessage::WarnCloseRoom())
+ return false;
+ if (auto room = room_network.GetRoom().lock()) {
+ // if you are in a room, leave it
+ if (auto member = room_network.GetRoomMember().lock()) {
+ member->Leave();
+ LOG_DEBUG(Frontend, "Left the room (as a client)");
+ }
+
+ // if you are hosting a room, also stop hosting
+ if (room->GetState() != Network::Room::State::Open) {
+ return true;
+ }
+ // Save ban list
+ UISettings::values.multiplayer_ban_list = std::move(room->GetBanList());
+
+ room->Destroy();
+ announce_multiplayer_session->Stop();
+ LOG_DEBUG(Frontend, "Closed the room (as a server)");
+ }
+ return true;
+}
+
+void MultiplayerState::ShowNotification() {
+ if (client_room && client_room->isAncestorOf(QApplication::focusWidget()))
+ return; // Do not show notification if the chat window currently has focus
+ show_notification = true;
+ QApplication::alert(nullptr);
+ QApplication::beep();
+ SetNotificationStatus(NotificationStatus::Notification);
+}
+
+void MultiplayerState::HideNotification() {
+ show_notification = false;
+ SetNotificationStatus(NotificationStatus::Connected);
+}
+
+void MultiplayerState::OnOpenNetworkRoom() {
+ if (auto member = room_network.GetRoomMember().lock()) {
+ if (member->IsConnected()) {
+ if (client_room == nullptr) {
+ client_room = new ClientRoomWindow(this, room_network);
+ connect(client_room, &ClientRoomWindow::ShowNotification, this,
+ &MultiplayerState::ShowNotification);
+ }
+ BringWidgetToFront(client_room);
+ return;
+ }
+ }
+ // If the user is not a member of a room, show the lobby instead.
+ // This is currently only used on the clickable label in the status bar
+ OnViewLobby();
+}
+
+void MultiplayerState::OnDirectConnectToRoom() {
+ if (direct_connect == nullptr) {
+ direct_connect = new DirectConnectWindow(system, this);
+ connect(direct_connect, &DirectConnectWindow::SaveConfig, this,
+ &MultiplayerState::OnSaveConfig);
+ }
+ BringWidgetToFront(direct_connect);
+}
+
+bool MultiplayerState::IsHostingPublicRoom() const {
+ return announce_multiplayer_session->IsRunning();
+}
+
+void MultiplayerState::UpdateCredentials() {
+ announce_multiplayer_session->UpdateCredentials();
+}
+
+void MultiplayerState::UpdateGameList(QStandardItemModel* game_list) {
+ game_list_model = game_list;
+ if (lobby) {
+ lobby->UpdateGameList(game_list);
+ }
+ if (host_room) {
+ host_room->UpdateGameList(game_list);
+ }
+}
diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h
new file mode 100644
index 000000000..5d681c5c6
--- /dev/null
+++ b/src/yuzu/multiplayer/state.h
@@ -0,0 +1,111 @@
+// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <QWidget>
+#include "network/announce_multiplayer_session.h"
+#include "network/network.h"
+
+class QStandardItemModel;
+class Lobby;
+class HostRoomWindow;
+class ClientRoomWindow;
+class DirectConnectWindow;
+class ClickableLabel;
+
+namespace Core {
+class System;
+}
+
+class MultiplayerState : public QWidget {
+ Q_OBJECT;
+
+public:
+ enum class NotificationStatus {
+ Unitialized,
+ Disconnected,
+ Connected,
+ Notification,
+ };
+
+ explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room,
+ QAction* show_room, Core::System& system_);
+ ~MultiplayerState();
+
+ /**
+ * Close all open multiplayer related dialogs
+ */
+ void Close();
+
+ void SetNotificationStatus(NotificationStatus state);
+
+ void UpdateNotificationStatus();
+
+ ClickableLabel* GetStatusText() const {
+ return status_text;
+ }
+
+ ClickableLabel* GetStatusIcon() const {
+ return status_icon;
+ }
+
+ void retranslateUi();
+
+ /**
+ * Whether a public room is being hosted or not.
+ * When this is true, Web Services configuration should be disabled.
+ */
+ bool IsHostingPublicRoom() const;
+
+ void UpdateCredentials();
+
+ /**
+ * Updates the multiplayer dialogs with a new game list model.
+ * This model should be the original model of the game list.
+ */
+ void UpdateGameList(QStandardItemModel* game_list);
+
+public slots:
+ void OnNetworkStateChanged(const Network::RoomMember::State& state);
+ void OnNetworkError(const Network::RoomMember::Error& error);
+ void OnViewLobby();
+ void OnCreateRoom();
+ bool OnCloseRoom();
+ void OnOpenNetworkRoom();
+ void OnDirectConnectToRoom();
+ void OnAnnounceFailed(const WebService::WebResult&);
+ void OnSaveConfig();
+ void UpdateThemedIcons();
+ void ShowNotification();
+ void HideNotification();
+
+signals:
+ void NetworkStateChanged(const Network::RoomMember::State&);
+ void NetworkError(const Network::RoomMember::Error&);
+ void AnnounceFailed(const WebService::WebResult&);
+ void SaveConfig();
+
+private:
+ Lobby* lobby = nullptr;
+ HostRoomWindow* host_room = nullptr;
+ ClientRoomWindow* client_room = nullptr;
+ DirectConnectWindow* direct_connect = nullptr;
+ ClickableLabel* status_icon = nullptr;
+ ClickableLabel* status_text = nullptr;
+ QStandardItemModel* game_list_model = nullptr;
+ QAction* leave_room;
+ QAction* show_room;
+ std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
+ Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized;
+ NotificationStatus notification_status = NotificationStatus::Unitialized;
+ bool has_mod_perms = false;
+ Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle;
+ Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle;
+
+ bool show_notification = false;
+ Core::System& system;
+ Network::RoomNetwork& room_network;
+};
+
+Q_DECLARE_METATYPE(WebService::WebResult);
diff --git a/src/yuzu/multiplayer/validation.h b/src/yuzu/multiplayer/validation.h
new file mode 100644
index 000000000..dabf860be
--- /dev/null
+++ b/src/yuzu/multiplayer/validation.h
@@ -0,0 +1,48 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <QRegExp>
+#include <QString>
+#include <QValidator>
+
+class Validation {
+public:
+ Validation()
+ : room_name(room_name_regex), nickname(nickname_regex), ip(ip_regex), port(0, UINT16_MAX) {}
+
+ ~Validation() = default;
+
+ const QValidator* GetRoomName() const {
+ return &room_name;
+ }
+ const QValidator* GetNickname() const {
+ return &nickname;
+ }
+ const QValidator* GetIP() const {
+ return &ip;
+ }
+ const QValidator* GetPort() const {
+ return &port;
+ }
+
+private:
+ /// room name can be alphanumeric and " " "_" "." and "-" and must have a size of 4-20
+ QRegExp room_name_regex = QRegExp(QStringLiteral("^[a-zA-Z0-9._- ]{4,20}$"));
+ QRegExpValidator room_name;
+
+ /// nickname can be alphanumeric and " " "_" "." and "-" and must have a size of 4-20
+ QRegExp nickname_regex = QRegExp(QStringLiteral("^[a-zA-Z0-9._- ]{4,20}$"));
+ QRegExpValidator nickname;
+
+ /// ipv4 address only
+ // TODO remove this when we support hostnames in direct connect
+ QRegExp ip_regex = QRegExp(QStringLiteral(
+ "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|"
+ "2[0-4][0-9]|25[0-5])"));
+ QRegExpValidator ip;
+
+ /// port must be between 0 and 65535
+ QIntValidator port;
+};
diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp
new file mode 100644
index 000000000..fc2693f9d
--- /dev/null
+++ b/src/yuzu/startup_checks.cpp
@@ -0,0 +1,158 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+#ifdef _WIN32
+#include <cstring> // for memset, strncpy
+#include <processthreadsapi.h>
+#include <windows.h>
+#elif defined(YUZU_UNIX)
+#include <errno.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+
+#include <cstdio>
+#include "video_core/vulkan_common/vulkan_instance.h"
+#include "video_core/vulkan_common/vulkan_library.h"
+#include "yuzu/startup_checks.h"
+
+void CheckVulkan() {
+ // Just start the Vulkan loader, this will crash if something is wrong
+ try {
+ Vulkan::vk::InstanceDispatch dld;
+ const Common::DynamicLibrary library = Vulkan::OpenLibrary();
+ const Vulkan::vk::Instance instance =
+ Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_0);
+
+ } catch (const Vulkan::vk::Exception& exception) {
+ std::fprintf(stderr, "Failed to initialize Vulkan: %s\n", exception.what());
+ }
+}
+
+bool CheckEnvVars(bool* is_child) {
+#ifdef _WIN32
+ // Check environment variable to see if we are the child
+ char variable_contents[8];
+ const DWORD startup_check_var =
+ GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8);
+ if (startup_check_var > 0 && std::strncmp(variable_contents, ENV_VAR_ENABLED_TEXT, 8) == 0) {
+ CheckVulkan();
+ return true;
+ }
+
+ // Don't perform startup checks if we are a child process
+ char is_child_s[8];
+ const DWORD is_child_len = GetEnvironmentVariableA(IS_CHILD_ENV_VAR, is_child_s, 8);
+ if (is_child_len > 0 && std::strncmp(is_child_s, ENV_VAR_ENABLED_TEXT, 8) == 0) {
+ *is_child = true;
+ return false;
+ } else if (!SetEnvironmentVariableA(IS_CHILD_ENV_VAR, ENV_VAR_ENABLED_TEXT)) {
+ std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n",
+ IS_CHILD_ENV_VAR, GetLastError());
+ return true;
+ }
+#endif
+ return false;
+}
+
+bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check) {
+#ifdef _WIN32
+ // Set the startup variable for child processes
+ const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT);
+ if (!env_var_set) {
+ std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n",
+ STARTUP_CHECK_ENV_VAR, GetLastError());
+ return false;
+ }
+
+ if (perform_vulkan_check) {
+ // Spawn child process that performs Vulkan check
+ PROCESS_INFORMATION process_info;
+ std::memset(&process_info, '\0', sizeof(process_info));
+
+ if (!SpawnChild(arg0, &process_info, 0)) {
+ return false;
+ }
+
+ // Wait until the processs exits and get exit code from it
+ WaitForSingleObject(process_info.hProcess, INFINITE);
+ DWORD exit_code = STILL_ACTIVE;
+ const int err = GetExitCodeProcess(process_info.hProcess, &exit_code);
+ if (err == 0) {
+ std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError());
+ }
+
+ // Vulkan is broken if the child crashed (return value is not zero)
+ *has_broken_vulkan = (exit_code != 0);
+
+ if (CloseHandle(process_info.hProcess) == 0) {
+ std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
+ }
+ if (CloseHandle(process_info.hThread) == 0) {
+ std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
+ }
+ }
+
+ if (!SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, nullptr)) {
+ std::fprintf(stderr, "SetEnvironmentVariableA failed to clear %s with error %d\n",
+ STARTUP_CHECK_ENV_VAR, GetLastError());
+ }
+
+#elif defined(YUZU_UNIX)
+ if (perform_vulkan_check) {
+ const pid_t pid = fork();
+ if (pid == 0) {
+ CheckVulkan();
+ return true;
+ } else if (pid == -1) {
+ const int err = errno;
+ std::fprintf(stderr, "fork failed with error %d\n", err);
+ return false;
+ }
+
+ // Get exit code from child process
+ int status;
+ const int r_val = wait(&status);
+ if (r_val == -1) {
+ const int err = errno;
+ std::fprintf(stderr, "wait failed with error %d\n", err);
+ return false;
+ }
+ // Vulkan is broken if the child crashed (return value is not zero)
+ *has_broken_vulkan = (status != 0);
+ }
+#endif
+ return false;
+}
+
+#ifdef _WIN32
+bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags) {
+ STARTUPINFOA startup_info;
+
+ std::memset(&startup_info, '\0', sizeof(startup_info));
+ startup_info.cb = sizeof(startup_info);
+
+ char p_name[255];
+ std::strncpy(p_name, arg0, 255);
+
+ const bool process_created = CreateProcessA(nullptr, // lpApplicationName
+ p_name, // lpCommandLine
+ nullptr, // lpProcessAttributes
+ nullptr, // lpThreadAttributes
+ false, // bInheritHandles
+ flags, // dwCreationFlags
+ nullptr, // lpEnvironment
+ nullptr, // lpCurrentDirectory
+ &startup_info, // lpStartupInfo
+ pi // lpProcessInformation
+ );
+ if (!process_created) {
+ std::fprintf(stderr, "CreateProcessA failed with error %d\n", GetLastError());
+ return false;
+ }
+
+ return true;
+}
+#endif
diff --git a/src/yuzu/startup_checks.h b/src/yuzu/startup_checks.h
new file mode 100644
index 000000000..d8e563be6
--- /dev/null
+++ b/src/yuzu/startup_checks.h
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
+constexpr char IS_CHILD_ENV_VAR[] = "YUZU_IS_CHILD";
+constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS";
+constexpr char ENV_VAR_ENABLED_TEXT[] = "ON";
+
+void CheckVulkan();
+bool CheckEnvVars(bool* is_child);
+bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check);
+
+#ifdef _WIN32
+bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags);
+#endif
diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp
index 21683576c..2c1b547fb 100644
--- a/src/yuzu/uisettings.cpp
+++ b/src/yuzu/uisettings.cpp
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "yuzu/uisettings.h"
@@ -15,6 +14,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..753797efc 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -17,6 +16,8 @@
namespace UISettings {
+bool IsDarkTheme();
+
struct ContextualShortcut {
QString keyseq;
QString controller_keyseq;
@@ -62,26 +63,28 @@ struct Values {
QByteArray gamelist_header_state;
QByteArray microprofile_geometry;
- Settings::BasicSetting<bool> microprofile_visible{false, "microProfileDialogVisible"};
+ Settings::Setting<bool> microprofile_visible{false, "microProfileDialogVisible"};
- Settings::BasicSetting<bool> single_window_mode{true, "singleWindowMode"};
- Settings::BasicSetting<bool> fullscreen{false, "fullscreen"};
- Settings::BasicSetting<bool> display_titlebar{true, "displayTitleBars"};
- Settings::BasicSetting<bool> show_filter_bar{true, "showFilterBar"};
- Settings::BasicSetting<bool> show_status_bar{true, "showStatusBar"};
+ Settings::Setting<bool> single_window_mode{true, "singleWindowMode"};
+ Settings::Setting<bool> fullscreen{false, "fullscreen"};
+ Settings::Setting<bool> display_titlebar{true, "displayTitleBars"};
+ Settings::Setting<bool> show_filter_bar{true, "showFilterBar"};
+ Settings::Setting<bool> show_status_bar{true, "showStatusBar"};
- Settings::BasicSetting<bool> confirm_before_closing{true, "confirmClose"};
- Settings::BasicSetting<bool> first_start{true, "firstStart"};
- Settings::BasicSetting<bool> pause_when_in_background{false, "pauseWhenInBackground"};
- Settings::BasicSetting<bool> mute_when_in_background{false, "muteWhenInBackground"};
- Settings::BasicSetting<bool> hide_mouse{true, "hideInactiveMouse"};
+ Settings::Setting<bool> confirm_before_closing{true, "confirmClose"};
+ Settings::Setting<bool> first_start{true, "firstStart"};
+ Settings::Setting<bool> pause_when_in_background{false, "pauseWhenInBackground"};
+ Settings::Setting<bool> mute_when_in_background{false, "muteWhenInBackground"};
+ Settings::Setting<bool> hide_mouse{true, "hideInactiveMouse"};
+ // Set when Vulkan is known to crash the application
+ bool has_broken_vulkan = false;
- Settings::BasicSetting<bool> select_user_on_boot{false, "select_user_on_boot"};
+ Settings::Setting<bool> select_user_on_boot{false, "select_user_on_boot"};
// Discord RPC
- Settings::BasicSetting<bool> enable_discord_presence{true, "enable_discord_presence"};
+ Settings::Setting<bool> enable_discord_presence{true, "enable_discord_presence"};
- Settings::BasicSetting<bool> enable_screenshot_save_as{true, "enable_screenshot_save_as"};
+ Settings::Setting<bool> enable_screenshot_save_as{true, "enable_screenshot_save_as"};
QString roms_path;
QString symbols_path;
@@ -96,24 +99,39 @@ struct Values {
// Shortcut name <Shortcut, context>
std::vector<Shortcut> shortcuts;
- Settings::BasicSetting<uint32_t> callout_flags{0, "calloutFlags"};
+ Settings::Setting<uint32_t> callout_flags{0, "calloutFlags"};
+
+ // multiplayer settings
+ Settings::Setting<QString> multiplayer_nickname{{}, "nickname"};
+ Settings::Setting<QString> multiplayer_ip{{}, "ip"};
+ Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, UINT16_MAX, "port"};
+ Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"};
+ Settings::Setting<QString> multiplayer_room_name{{}, "room_name"};
+ Settings::SwitchableSetting<uint, true> multiplayer_max_player{8, 0, 8, "max_player"};
+ Settings::SwitchableSetting<uint, true> multiplayer_room_port{24872, 0, UINT16_MAX,
+ "room_port"};
+ Settings::SwitchableSetting<uint, true> multiplayer_host_type{0, 0, 1, "host_type"};
+ Settings::Setting<qulonglong> multiplayer_game_id{{}, "game_id"};
+ Settings::Setting<QString> multiplayer_room_description{{}, "room_description"};
+ std::pair<std::vector<std::string>, std::vector<std::string>> multiplayer_ban_list;
// logging
- Settings::BasicSetting<bool> show_console{false, "showConsole"};
+ Settings::Setting<bool> show_console{false, "showConsole"};
// Game List
- Settings::BasicSetting<bool> show_add_ons{true, "show_add_ons"};
- Settings::BasicSetting<uint32_t> game_icon_size{64, "game_icon_size"};
- Settings::BasicSetting<uint32_t> folder_icon_size{48, "folder_icon_size"};
- Settings::BasicSetting<uint8_t> row_1_text_id{3, "row_1_text_id"};
- Settings::BasicSetting<uint8_t> row_2_text_id{2, "row_2_text_id"};
+ Settings::Setting<bool> show_add_ons{true, "show_add_ons"};
+ Settings::Setting<uint32_t> game_icon_size{64, "game_icon_size"};
+ Settings::Setting<uint32_t> folder_icon_size{48, "folder_icon_size"};
+ Settings::Setting<uint8_t> row_1_text_id{3, "row_1_text_id"};
+ Settings::Setting<uint8_t> row_2_text_id{2, "row_2_text_id"};
std::atomic_bool is_game_list_reload_pending{false};
- Settings::BasicSetting<bool> cache_game_list{true, "cache_game_list"};
- Settings::BasicSetting<bool> favorites_expanded{true, "favorites_expanded"};
+ Settings::Setting<bool> cache_game_list{true, "cache_game_list"};
+ Settings::Setting<bool> favorites_expanded{true, "favorites_expanded"};
QVector<u64> favorited_ids;
bool configuration_applied;
bool reset_to_defaults;
+ Settings::Setting<bool> disable_web_applet{true, "disable_web_applet"};
};
extern Values values;
diff --git a/src/yuzu/util/clickable_label.cpp b/src/yuzu/util/clickable_label.cpp
new file mode 100644
index 000000000..89d14190a
--- /dev/null
+++ b/src/yuzu/util/clickable_label.cpp
@@ -0,0 +1,11 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "yuzu/util/clickable_label.h"
+
+ClickableLabel::ClickableLabel(QWidget* parent, [[maybe_unused]] Qt::WindowFlags f)
+ : QLabel(parent) {}
+
+void ClickableLabel::mouseReleaseEvent([[maybe_unused]] QMouseEvent* event) {
+ emit clicked();
+}
diff --git a/src/yuzu/util/clickable_label.h b/src/yuzu/util/clickable_label.h
new file mode 100644
index 000000000..4fe744150
--- /dev/null
+++ b/src/yuzu/util/clickable_label.h
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <QLabel>
+#include <QWidget>
+
+class ClickableLabel : public QLabel {
+ Q_OBJECT
+
+public:
+ explicit ClickableLabel(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
+ ~ClickableLabel() = default;
+
+signals:
+ void clicked();
+
+protected:
+ void mouseReleaseEvent(QMouseEvent* event);
+};
diff --git a/src/yuzu/util/controller_navigation.cpp b/src/yuzu/util/controller_navigation.cpp
index c2b13123d..d49ae67cd 100644
--- a/src/yuzu/util/controller_navigation.cpp
+++ b/src/yuzu/util/controller_navigation.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings_input.h"
#include "core/hid/emulated_controller.h"
@@ -39,7 +38,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/util/controller_navigation.h b/src/yuzu/util/controller_navigation.h
index 7c616a088..86e210368 100644
--- a/src/yuzu/util/controller_navigation.h
+++ b/src/yuzu/util/controller_navigation.h
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/util/limitable_input_dialog.cpp b/src/yuzu/util/limitable_input_dialog.cpp
index 6fea41f95..bbb370595 100644
--- a/src/yuzu/util/limitable_input_dialog.cpp
+++ b/src/yuzu/util/limitable_input_dialog.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QDialogButtonBox>
#include <QLabel>
diff --git a/src/yuzu/util/limitable_input_dialog.h b/src/yuzu/util/limitable_input_dialog.h
index a8e31098b..f261f1a0f 100644
--- a/src/yuzu/util/limitable_input_dialog.h
+++ b/src/yuzu/util/limitable_input_dialog.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/util/overlay_dialog.cpp b/src/yuzu/util/overlay_dialog.cpp
index c66dfbdff..b27954512 100644
--- a/src/yuzu/util/overlay_dialog.cpp
+++ b/src/yuzu/util/overlay_dialog.cpp
@@ -1,6 +1,5 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QKeyEvent>
#include <QScreen>
diff --git a/src/yuzu/util/overlay_dialog.h b/src/yuzu/util/overlay_dialog.h
index d8a140ff3..39c44393c 100644
--- a/src/yuzu/util/overlay_dialog.h
+++ b/src/yuzu/util/overlay_dialog.h
@@ -1,10 +1,8 @@
-// Copyright 2021 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <array>
#include <atomic>
#include <memory>
#include <thread>
diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
index bb5f74ec4..4b10fa517 100644
--- a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
+++ b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <QDialogButtonBox>
#include <QKeySequenceEdit>
diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.h b/src/yuzu/util/sequence_dialog/sequence_dialog.h
index 969c77740..85e146d40 100644
--- a/src/yuzu/util/sequence_dialog/sequence_dialog.h
+++ b/src/yuzu/util/sequence_dialog/sequence_dialog.h
@@ -1,6 +1,5 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2018 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/util/url_request_interceptor.cpp b/src/yuzu/util/url_request_interceptor.cpp
index b637e771e..996097e35 100644
--- a/src/yuzu/util/url_request_interceptor.cpp
+++ b/src/yuzu/util/url_request_interceptor.cpp
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef YUZU_USE_QT_WEB_ENGINE
diff --git a/src/yuzu/util/url_request_interceptor.h b/src/yuzu/util/url_request_interceptor.h
index 8a7f7499f..9831e1523 100644
--- a/src/yuzu/util/url_request_interceptor.h
+++ b/src/yuzu/util/url_request_interceptor.h
@@ -1,6 +1,5 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index ef31bc2d2..5c3e4589e 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <cmath>
diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h
index e6790f260..39dd2d895 100644
--- a/src/yuzu/util/util.h
+++ b/src/yuzu/util/util.h
@@ -1,6 +1,5 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/yuzu.qrc b/src/yuzu/yuzu.qrc
index 5733cac98..855df05fd 100644
--- a/src/yuzu/yuzu.qrc
+++ b/src/yuzu/yuzu.qrc
@@ -1,3 +1,8 @@
+<!--
+SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+SPDX-License-Identifier: GPL-2.0-or-later
+-->
+
<RCC>
<qresource prefix="/img">
<file alias="yuzu.ico">../../dist/yuzu.ico</file>
diff --git a/src/yuzu/yuzu.rc b/src/yuzu/yuzu.rc
index 4a3645a71..1fc74d065 100644
--- a/src/yuzu/yuzu.rc
+++ b/src/yuzu/yuzu.rc
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
#include "winresrc.h"
/////////////////////////////////////////////////////////////////////////////
//
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index 74fc24972..7d8ca3d8a 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
# Credits to Samantas5855 and others for this function.
@@ -45,7 +48,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/config.cpp b/src/yuzu_cmd/config.cpp
index ff616da70..66dd0dc15 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -1,8 +1,8 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
+#include <optional>
#include <sstream>
// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
@@ -20,7 +20,6 @@
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
-#include "common/param_package.h"
#include "common/settings.h"
#include "core/hle/service/acc/profile_manager.h"
#include "input_common/main.h"
@@ -29,11 +28,12 @@
namespace FS = Common::FS;
-Config::Config() {
- // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
- sdl2_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "sdl2-config.ini";
- sdl2_config = std::make_unique<INIReader>(FS::PathToUTF8String(sdl2_config_loc));
+const std::filesystem::path default_config_path =
+ FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "sdl2-config.ini";
+Config::Config(std::optional<std::filesystem::path> config_path)
+ : sdl2_config_loc{config_path.value_or(default_config_path)},
+ sdl2_config{std::make_unique<INIReader>(FS::PathToUTF8String(sdl2_config_loc))} {
Reload();
}
@@ -89,17 +89,17 @@ static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs>
}};
template <>
-void Config::ReadSetting(const std::string& group, Settings::BasicSetting<std::string>& setting) {
+void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
setting = sdl2_config->Get(group, setting.GetLabel(), setting.GetDefault());
}
template <>
-void Config::ReadSetting(const std::string& group, Settings::BasicSetting<bool>& setting) {
+void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
setting = sdl2_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
}
-template <typename Type>
-void Config::ReadSetting(const std::string& group, Settings::BasicSetting<Type>& setting) {
+template <typename Type, bool ranged>
+void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
setting = static_cast<Type>(sdl2_config->GetInteger(group, setting.GetLabel(),
static_cast<long>(setting.GetDefault())));
}
@@ -266,6 +266,7 @@ void Config::ReadValues() {
// Core
ReadSetting("Core", Settings::values.use_multi_core);
+ ReadSetting("Core", Settings::values.use_extended_memory_layout);
// Cpu
ReadSetting("Cpu", Settings::values.cpu_accuracy);
@@ -279,11 +280,14 @@ void Config::ReadValues() {
ReadSetting("Cpu", Settings::values.cpuopt_misc_ir);
ReadSetting("Cpu", Settings::values.cpuopt_reduce_misalign_checks);
ReadSetting("Cpu", Settings::values.cpuopt_fastmem);
+ ReadSetting("Cpu", Settings::values.cpuopt_fastmem_exclusives);
+ ReadSetting("Cpu", Settings::values.cpuopt_recompile_exclusives);
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_unfuse_fma);
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_reduce_fp_error);
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_standard_fpcr);
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_inaccurate_nan);
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_fastmem_check);
+ ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_global_monitor);
// Renderer
ReadSetting("Renderer", Settings::values.renderer_backend);
@@ -305,13 +309,12 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.gpu_accuracy);
ReadSetting("Renderer", Settings::values.use_asynchronous_gpu_emulation);
ReadSetting("Renderer", Settings::values.use_vsync);
- ReadSetting("Renderer", Settings::values.fps_cap);
- ReadSetting("Renderer", Settings::values.disable_fps_limit);
ReadSetting("Renderer", Settings::values.shader_backend);
ReadSetting("Renderer", Settings::values.use_asynchronous_shaders);
ReadSetting("Renderer", Settings::values.nvdec_emulation);
ReadSetting("Renderer", Settings::values.accelerate_astc);
ReadSetting("Renderer", Settings::values.use_fast_gpu_time);
+ ReadSetting("Renderer", Settings::values.use_pessimistic_flushes);
ReadSetting("Renderer", Settings::values.bg_red);
ReadSetting("Renderer", Settings::values.bg_green);
@@ -319,7 +322,7 @@ void Config::ReadValues() {
// Audio
ReadSetting("Audio", Settings::values.sink_id);
- ReadSetting("Audio", Settings::values.audio_device_id);
+ ReadSetting("Audio", Settings::values.audio_output_device_id);
ReadSetting("Audio", Settings::values.volume);
// Miscellaneous
@@ -339,6 +342,8 @@ void Config::ReadValues() {
ReadSetting("Debugging", Settings::values.use_debug_asserts);
ReadSetting("Debugging", Settings::values.use_auto_stub);
ReadSetting("Debugging", Settings::values.disable_macro_jit);
+ ReadSetting("Debugging", Settings::values.use_gdbstub);
+ ReadSetting("Debugging", Settings::values.gdbstub_port);
const auto title_list = sdl2_config->Get("AddOns", "title_ids", "");
std::stringstream ss(title_list);
diff --git a/src/yuzu_cmd/config.h b/src/yuzu_cmd/config.h
index 1ee932be2..021438b17 100644
--- a/src/yuzu_cmd/config.h
+++ b/src/yuzu_cmd/config.h
@@ -1,11 +1,11 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <memory>
+#include <optional>
#include <string>
#include "common/settings.h"
@@ -13,25 +13,25 @@
class INIReader;
class Config {
- std::unique_ptr<INIReader> sdl2_config;
std::filesystem::path sdl2_config_loc;
+ std::unique_ptr<INIReader> sdl2_config;
bool LoadINI(const std::string& default_contents = "", bool retry = true);
void ReadValues();
public:
- Config();
+ explicit Config(std::optional<std::filesystem::path> config_path);
~Config();
void Reload();
private:
/**
- * Applies a value read from the sdl2_config to a BasicSetting.
+ * Applies a value read from the sdl2_config to a Setting.
*
* @param group The name of the INI group
* @param setting The yuzu setting to modify
*/
- template <typename Type>
- void ReadSetting(const std::string& group, Settings::BasicSetting<Type>& setting);
+ template <typename Type, bool ranged>
+ void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
};
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 6d613bf7a..d214771b0 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -1,6 +1,5 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -124,7 +123,11 @@ keyboard_enabled =
[Core]
# Whether to use multi-core for CPU emulation
# 0: Disabled, 1 (default): Enabled
-use_multi_core=
+use_multi_core =
+
+# Enable extended guest system memory layout (6GB DRAM)
+# 0 (default): Disabled, 1: Enabled
+use_extended_memory_layout =
[Cpu]
# Adjusts various optimizations.
@@ -174,6 +177,14 @@ cpuopt_reduce_misalign_checks =
# 0: Disabled, 1 (default): Enabled
cpuopt_fastmem =
+# Enable Host MMU Emulation for exclusive memory instructions (faster guest memory access)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_fastmem_exclusives =
+
+# Enable fallback on failure of fastmem of exclusive memory instructions (faster guest memory access)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_recompile_exclusives =
+
# Enable unfuse FMA (improve performance on CPUs without FMA)
# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
# 0: Disabled, 1 (default): Enabled
@@ -199,9 +210,14 @@ cpuopt_unsafe_inaccurate_nan =
# 0: Disabled, 1 (default): Enabled
cpuopt_unsafe_fastmem_check =
+# Enable faster exclusive instructions
+# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
+# 0: Disabled, 1 (default): Enabled
+cpuopt_unsafe_ignore_global_monitor =
+
[Renderer]
# Which backend API to use.
-# 0 (default): OpenGL, 1: Vulkan
+# 0: OpenGL, 1 (default): Vulkan
backend =
# Enable graphics API debugging mode.
@@ -303,6 +319,10 @@ use_asynchronous_gpu_emulation =
# 0: Off, 1 (default): On
use_fast_gpu_time =
+# Force unmodified buffers to be flushed, which can cost performance.
+# 0: Off (default), 1: On
+use_pessimistic_flushes =
+
# Whether to use garbage collection or not for GPU caches.
# 0 (default): Off, 1: On
use_caches_gc =
@@ -313,10 +333,6 @@ bg_red =
bg_blue =
bg_green =
-# Caps the unlocked framerate to a multiple of the title's target FPS.
-# 1 - 1000: Target FPS multiple cap. 1000 (default)
-fps_cap =
-
[Audio]
# Which audio output engine to use.
# auto (default): Auto-select
@@ -325,12 +341,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 =
@@ -423,9 +433,11 @@ use_debug_asserts =
use_auto_stub =
# Enables/Disables the macro JIT compiler
disable_macro_jit=false
-# Presents guest frames as they become available. Experimental.
+# Determines whether to enable the GDB stub and wait for the debugger to attach before running.
# false: Disabled (default), true: Enabled
-disable_fps_limit=false
+use_gdbstub=false
+# The port to use for the GDB server, if it is enabled.
+gdbstub_port=6543
[WebService]
# Whether or not to enable telemetry
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 57f807826..4ac72c2f6 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -1,6 +1,5 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <SDL.h>
@@ -93,7 +92,7 @@ void EmuWindow_SDL2::OnFingerMotion(float x, float y, std::size_t id) {
}
void EmuWindow_SDL2::OnFingerUp() {
- input_subsystem->GetTouchScreen()->TouchReleased(0);
+ input_subsystem->GetTouchScreen()->ReleaseAllTouch();
}
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
@@ -123,14 +122,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) {
@@ -161,7 +161,15 @@ void EmuWindow_SDL2::WaitEvent() {
SDL_Event event;
if (!SDL_WaitEvent(&event)) {
- LOG_CRITICAL(Frontend, "SDL_WaitEvent failed: {}", SDL_GetError());
+ const char* error = SDL_GetError();
+ if (!error || strcmp(error, "") == 0) {
+ // https://github.com/libsdl-org/SDL/issues/5780
+ // Sometimes SDL will return without actually having hit an error condition;
+ // just ignore it in this case.
+ return;
+ }
+
+ LOG_CRITICAL(Frontend, "SDL_WaitEvent failed: {}", error);
exit(1);
}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index 0af002693..90bb0b415 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -1,10 +1,8 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <memory>
#include <utility>
#include "core/frontend/emu_window.h"
@@ -21,7 +19,7 @@ enum class MouseButton;
class EmuWindow_SDL2 : public Core::Frontend::EmuWindow {
public:
- explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem, Core::System& system_);
+ explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_);
~EmuWindow_SDL2();
/// Whether the window is still open, and a close request hasn't yet been sent
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
index 70db865ec..9b660c13c 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstdlib>
@@ -11,7 +10,6 @@
#include <fmt/format.h>
#include <glad/glad.h>
-#include "common/assert.h"
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "common/settings.h"
@@ -75,9 +73,9 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
return unsupported_ext.empty();
}
-EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem,
+EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem_,
Core::System& system_, bool fullscreen)
- : EmuWindow_SDL2{input_subsystem, system_} {
+ : EmuWindow_SDL2{input_subsystem_, system_} {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
index d7f2c83d8..39346e704 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
@@ -1,6 +1,5 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -18,7 +17,7 @@ class InputSubsystem;
class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 {
public:
- explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, Core::System& system_,
+ explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_,
bool fullscreen);
~EmuWindow_SDL2_GL();
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
index de40b76bf..65455c86e 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdlib>
#include <memory>
@@ -8,10 +7,8 @@
#include <fmt/format.h>
-#include "common/assert.h"
#include "common/logging/log.h"
#include "common/scm_rev.h"
-#include "common/settings.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
@@ -24,9 +21,9 @@
#include <SDL.h>
#include <SDL_syswm.h>
-EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem,
+EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem_,
Core::System& system_, bool fullscreen)
- : EmuWindow_SDL2{input_subsystem, system_} {
+ : EmuWindow_SDL2{input_subsystem_, system_} {
const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc);
render_window =
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
index 3ea521b2a..e39ad754d 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -19,7 +18,7 @@ class InputSubsystem;
class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
public:
- explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem, Core::System& system,
+ explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem_, Core::System& system,
bool fullscreen);
~EmuWindow_SDL2_VK() override;
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index b44ea0cc4..3a0f33cba 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -1,21 +1,17 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <iostream>
#include <memory>
+#include <regex>
#include <string>
#include <thread>
#include <fmt/ostream.h>
#include "common/detached_tasks.h"
-#include "common/fs/fs.h"
-#include "common/fs/fs_paths.h"
-#include "common/fs/path_util.h"
#include "common/logging/backend.h"
-#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/nvidia_flags.h"
@@ -25,6 +21,7 @@
#include "common/string_util.h"
#include "common/telemetry.h"
#include "core/core.h"
+#include "core/cpu_manager.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/vfs_real.h"
@@ -32,6 +29,7 @@
#include "core/loader/loader.h"
#include "core/telemetry_session.h"
#include "input_common/main.h"
+#include "network/network.h"
#include "video_core/renderer_base.h"
#include "yuzu_cmd/config.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
@@ -63,22 +61,122 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
static void PrintHelp(const char* argv0) {
std::cout << "Usage: " << argv0
<< " [options] <filename>\n"
+ "-m, --multiplayer=nick:password@address:port"
+ " Nickname, password, address and port for multiplayer\n"
"-f, --fullscreen Start in fullscreen mode\n"
"-h, --help Display this help and exit\n"
"-v, --version Output version information and exit\n"
- "-p, --program Pass following string as arguments to executable\n";
+ "-p, --program Pass following string as arguments to executable\n"
+ "-c, --config Load the specified configuration file\n";
}
static void PrintVersion() {
std::cout << "yuzu " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl;
}
+static void OnStateChanged(const Network::RoomMember::State& state) {
+ switch (state) {
+ case Network::RoomMember::State::Idle:
+ LOG_DEBUG(Network, "Network is idle");
+ break;
+ case Network::RoomMember::State::Joining:
+ LOG_DEBUG(Network, "Connection sequence to room started");
+ break;
+ case Network::RoomMember::State::Joined:
+ LOG_DEBUG(Network, "Successfully joined to the room");
+ break;
+ case Network::RoomMember::State::Moderator:
+ LOG_DEBUG(Network, "Successfully joined the room as a moderator");
+ break;
+ default:
+ break;
+ }
+}
+
+static void OnNetworkError(const Network::RoomMember::Error& error) {
+ switch (error) {
+ case Network::RoomMember::Error::LostConnection:
+ LOG_DEBUG(Network, "Lost connection to the room");
+ break;
+ case Network::RoomMember::Error::CouldNotConnect:
+ LOG_ERROR(Network, "Error: Could not connect");
+ exit(1);
+ break;
+ case Network::RoomMember::Error::NameCollision:
+ LOG_ERROR(
+ Network,
+ "You tried to use the same nickname as another user that is connected to the Room");
+ exit(1);
+ break;
+ case Network::RoomMember::Error::IpCollision:
+ LOG_ERROR(Network, "You tried to use the same fake IP-Address as another user that is "
+ "connected to the Room");
+ exit(1);
+ break;
+ case Network::RoomMember::Error::WrongPassword:
+ LOG_ERROR(Network, "Room replied with: Wrong password");
+ exit(1);
+ break;
+ case Network::RoomMember::Error::WrongVersion:
+ LOG_ERROR(Network,
+ "You are using a different version than the room you are trying to connect to");
+ exit(1);
+ break;
+ case Network::RoomMember::Error::RoomIsFull:
+ LOG_ERROR(Network, "The room is full");
+ exit(1);
+ break;
+ case Network::RoomMember::Error::HostKicked:
+ LOG_ERROR(Network, "You have been kicked by the host");
+ break;
+ case Network::RoomMember::Error::HostBanned:
+ LOG_ERROR(Network, "You have been banned by the host");
+ break;
+ case Network::RoomMember::Error::UnknownError:
+ LOG_ERROR(Network, "UnknownError");
+ break;
+ case Network::RoomMember::Error::PermissionDenied:
+ LOG_ERROR(Network, "PermissionDenied");
+ break;
+ case Network::RoomMember::Error::NoSuchUser:
+ LOG_ERROR(Network, "NoSuchUser");
+ break;
+ }
+}
+
+static void OnMessageReceived(const Network::ChatEntry& msg) {
+ std::cout << std::endl << msg.nickname << ": " << msg.message << std::endl << std::endl;
+}
+
+static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) {
+ std::string message;
+ switch (msg.type) {
+ case Network::IdMemberJoin:
+ message = fmt::format("{} has joined", msg.nickname);
+ break;
+ case Network::IdMemberLeave:
+ message = fmt::format("{} has left", msg.nickname);
+ break;
+ case Network::IdMemberKicked:
+ message = fmt::format("{} has been kicked", msg.nickname);
+ break;
+ case Network::IdMemberBanned:
+ message = fmt::format("{} has been banned", msg.nickname);
+ break;
+ case Network::IdAddressUnbanned:
+ message = fmt::format("{} has been unbanned", msg.nickname);
+ break;
+ }
+ if (!message.empty())
+ std::cout << std::endl << "* " << message << std::endl << std::endl;
+}
+
/// Application entry point
int main(int argc, char** argv) {
Common::Log::Initialize();
Common::Log::SetColorConsoleBackendEnabled(true);
+ Common::Log::Start();
Common::DetachedTasks detached_tasks;
- Config config;
int option_index = 0;
#ifdef _WIN32
@@ -91,21 +189,64 @@ int main(int argc, char** argv) {
}
#endif
std::string filepath;
+ std::optional<std::string> config_path;
+ std::string program_args;
+ bool use_multiplayer = false;
bool fullscreen = false;
+ std::string nickname{};
+ std::string password{};
+ std::string address{};
+ u16 port = Network::DefaultRoomPort;
static struct option long_options[] = {
+ // clang-format off
+ {"multiplayer", required_argument, 0, 'm'},
{"fullscreen", no_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'},
{"program", optional_argument, 0, 'p'},
+ {"config", required_argument, 0, 'c'},
{0, 0, 0, 0},
+ // clang-format on
};
while (optind < argc) {
- int arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index);
+ int arg = getopt_long(argc, argv, "g:fhvp::c:", long_options, &option_index);
if (arg != -1) {
switch (static_cast<char>(arg)) {
+ case 'm': {
+ use_multiplayer = true;
+ const std::string str_arg(optarg);
+ // regex to check if the format is nickname:password@ip:port
+ // with optional :password
+ const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
+ if (!std::regex_match(str_arg, re)) {
+ std::cout << "Wrong format for option --multiplayer\n";
+ PrintHelp(argv[0]);
+ return 0;
+ }
+
+ std::smatch match;
+ std::regex_search(str_arg, match, re);
+ ASSERT(match.size() == 5);
+ nickname = match[1];
+ password = match[2];
+ address = match[3];
+ if (!match[4].str().empty())
+ port = std::stoi(match[4]);
+ std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
+ if (!std::regex_match(nickname, nickname_re)) {
+ std::cout
+ << "Nickname is not valid. Must be 4 to 20 alphanumeric characters.\n";
+ return 0;
+ }
+ if (address.empty()) {
+ std::cout << "Address to room must not be empty.\n";
+ return 0;
+ }
+ break;
+ }
case 'f':
fullscreen = true;
LOG_INFO(Frontend, "Starting in fullscreen mode...");
@@ -117,9 +258,12 @@ int main(int argc, char** argv) {
PrintVersion();
return 0;
case 'p':
- Settings::values.program_args = argv[optind];
+ program_args = argv[optind];
++optind;
break;
+ case 'c':
+ config_path = optarg;
+ break;
}
} else {
#ifdef _WIN32
@@ -131,6 +275,18 @@ int main(int argc, char** argv) {
}
}
+ Config config{config_path};
+
+ // apply the log_filter setting
+ // the logger was initialized before and doesn't pick up the filter on its own
+ Common::Log::Filter filter;
+ filter.ParseFilterString(Settings::values.log_filter.GetValue());
+ Common::Log::SetGlobalFilter(filter);
+
+ if (!program_args.empty()) {
+ Settings::values.program_args = program_args;
+ }
+
#ifdef _WIN32
LocalFree(argv_w);
#endif
@@ -197,8 +353,24 @@ int main(int argc, char** argv) {
system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "SDL");
+ if (use_multiplayer) {
+ if (auto member = system.GetRoomNetwork().GetRoomMember().lock()) {
+ member->BindOnChatMessageRecieved(OnMessageReceived);
+ member->BindOnStatusMessageReceived(OnStatusMessageReceived);
+ member->BindOnStateChanged(OnStateChanged);
+ member->BindOnError(OnNetworkError);
+ LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port,
+ nickname);
+ member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password);
+ } else {
+ LOG_ERROR(Network, "Could not access RoomMember");
+ return 0;
+ }
+ }
+
// Core is loaded, start the GPU (makes the GPU contexts current to this thread)
system.GPU().Start();
+ system.GetCpuManager().OnGpuReady();
if (Settings::values.use_disk_shader_cache.GetValue()) {
system.Renderer().ReadRasterizer()->LoadDiskResources(
@@ -206,10 +378,19 @@ int main(int argc, char** argv) {
[](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
}
+ system.RegisterExitCallback([&] {
+ // Just exit right away.
+ exit(0);
+ });
+
void(system.Run());
+ if (system.DebuggerEnabled()) {
+ system.InitializeDebugger();
+ }
while (emu_window->IsOpen()) {
emu_window->WaitEvent();
}
+ system.DetachDebugger();
void(system.Pause());
system.Shutdown();
diff --git a/src/yuzu_cmd/yuzu.rc b/src/yuzu_cmd/yuzu.rc
index 0cde75e2f..e230cf680 100644
--- a/src/yuzu_cmd/yuzu.rc
+++ b/src/yuzu_cmd/yuzu.rc
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
#include "winresrc.h"
/////////////////////////////////////////////////////////////////////////////
//